首页 > 编程语言 >并发编程-4.用户界面响应能力和线程

并发编程-4.用户界面响应能力和线程

时间:2023-10-14 11:22:37浏览次数:32  
标签:Task 用户界面 编程 Order 线程 IsArchived new orders

利用后台线程

在第一章中,我们学习了如何创建后台线程并讨论了它们的一些用途。 后台线程的优先级低于进程的主线程和其他线程池线程。此外,活动的后台线程不会阻止用户或系统终止应用程序。

这意味着后台线程非常适合执行以下任务:

• 写入日志和分析数据
• 监控网络或文件系统资源
• 将数据读入应用程序

不要将后台线程用于关键应用程序操作,如下所示:

• 保存应用程序状态
• 执行数据库事务
• 应用程序数据处理

在决定某些工作是否可以由后台线程处理时,要遵循的一个好规则是问问自己,突然中断工作以关闭应用程序是否会危及系统的数据完整性。 那么,您如何知道您正在创建后台线程还是前台线程呢?

哪些线程是后台线程?

我们了解到,可以通过将线程的 IsBackground 属性设置为 true 来将其显式创建为后台线程。 默认情况下,通过调用 Thread 构造函数创建的所有其他线程都是前台线程。 应用程序的主线程是前台线程。所有 ThreadPool 线程都是后台线程。 这包括由任务并行库 (TPL) 启动的所有异步操作。

因此,如果所有基于任务的操作(例如异步方法)都在后台线程上执行,您是否应该避免使用它们来保存重要的应用程序数据? 当这些异步/等待操作正在进行时,.NET 是否允许您的应用程序关闭? 如果有前台线程正在等待异步操作,则应用程序在操作完成之前不会终止。 如果不使用await,或者使用Task.Run 在线程池上启动操作,则应用程序可能会在操作完成之前正常终止。

await 与异步方法结合使用的好处是,您可以灵活地控制执行流程,同时保持UI 响应。 让我们讨论客户端应用程序中的异步和等待,并创建一个从多个源加载数据的 Windows Presentation Foundation (WPF) 应用程序示例。

使用 async、await、tasks 和 WhenAll

在代码中使用 asyncwait 是使用 ThreadPool 引入一些后台工作的最简单方法。 异步方法必须使用 async 关键字修饰,并且将返回 System.Threading.Tasks.Task 类型而不是 void 返回。

异步方法返回 Task,以便调用方法可以等待该方法的结果。 如果要创建具有 void 返回类型的异步方法,则无法等待它,并且调用代码将在异步方法完成之前继续处理后续代码。 需要注意的是,只有事件处理程序才应声明为具有 void 返回类型的异步函数

如果该方法返回字符串,则异步等效项将返回 Task<string> 泛型类型。 让我们看一下每个例子:

private async Task ProcessDataAsync()
{
	// Process data here
}
private async Task<string> GetStringDataAsync()
{
    string stringData;
    // Build string here
    ...
    return stringData;
}

当您调用异步方法时,需要遵循两种常见模式。
• 首先,您可以等待调用并将返回类型设置为方法内返回类型的变量:

await ProcessDataAsync();
string data = await GetStringDataAsync();

• 第二个选项是在调用方法时使用任务变量并稍后等待它们:

Task dataTask = ProcessDataAsync();
Task<string> stringDataTask = GetStringDataAsync();

DoSomeOtherSynchronousWork();

string data = await stringDataTask;
await dataTask;

使用第二种方法,应用程序可以执行一些同步工作,同时两个异步方法继续在后台线程上运行。 一旦同步工作完成,应用程序将等待两个异步方法。

让我们将异步知识应用到一个更实际的示例项目中。 在此示例中,我们将使用 WPF 创建一个新的 Windows 客户端应用程序,该应用程序从两个异步方法加载数据。 我们将通过使用 Task.Delay 注入非阻塞延迟来模拟缓慢的服务调用以在这些方法中获取数据。 每个方法都需要几秒钟的时间来返回其数据,但 UI 将保持对用户输入的响应:

  1. 首先在 Visual Studio 中创建一个新的 WPF 项目。 将项目命名为 AwaitWithWpf
  2. 将两个新类添加到名为 OrderMainViewModel 的项目中。 您的解决方案现在应该如下所示:

图 4.1 – Visual Studio 中的 AwaitWithWpf 解决方案

image

  1. 接下来,打开 NuGet Package Manager,在 Browse 选项卡上搜索 MVVM Toolkit,将 Microsoft.Toolkit.Mvvm 包的最新稳定版本添加到您的项目中:

图 4.2 – 将 Microsoft.Toolkit.Mvvm 包添加到项目中

image

我们将使用 MVVM 工具包将模型-视图-视图模型 (MVVM) 功能添加到我们的 MainViewModel 类中。

  1. 现在,打开 Order 类并添加以下实现:
public class Order
{
    public int OrderId { get; set; }
    public string? CustomerName { get; set; }
    public bool IsArchived { get; set; }
}

当订单列表填充到主窗口上时,这将为每个订单提供一些要显示的属性。
5. 现在我们将开始构建 MainViewModel 实现。 第一步是添加要绑定到 UI 的订单列表以及要加载订单时要执行的命令:

public class MainViewModel : ObservableObject
{
    private ObservableCollection<Order> _orders = new();
    
    public MainViewModel()
    {
    	LoadOrderDataCommand = new AsyncRelayCommand(LoadOrderDataAsync);
    }
    
    public ICommand LoadOrderDataCommand { get; set; }
    public ObservableCollection<Order> Orders
    {
        get { return _orders; }
        set
        {
            SetProperty(ref _orders, value);
        }
    }
    private async Task LoadOrderDataAsync()
    {
    	// TODO – Add code to load orders
    }
}

在继续下一步之前,让我们回顾一下 MainViewModel 类的一些属性:

MainViewModel 类继承自MVVM Toolkit 提供的ObservableObject 类型。
• 此基类实现 INotifyPropertyChanged 接口,WPF 数据绑定使用该接口在数据绑定属性值更改时通知 UI。
• Orders 属性将通过WPF 数据绑定向UI 提供订单列表。在ObservableObject 基础上调用SetProperty 可设置_orders 支持变量的值并触发属性更改通知。
LoadOrderDataCommand 属性将由MainWindow 上的按钮执行。在构造函数中,该属性被初始化为新的AsyncRelayCommand,当UI 调用该命令时,该属性将调用LoadOrderDataAsync

  1. 不要忘记向类中添加必要的 using 语句:
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
  1. 接下来,我们创建两个异步方法来加载订单数据。 一个将创建当前订单,另一个将创建存档订单列表。 它们通过 Order 类的 IsArchived 属性来区分。 每个方法都使用 Task.Delay 来模拟通过慢速互联网或网络连接的服务调用:
private async Task<List<Order>> GetCurrentOrdersAsync()
{
    var orders = new List<Order>();
    await Task.Delay(4000);
    orders.Add(new Order { OrderId = 55, CustomerName = "Tony", IsArchived = false });
    orders.Add(new Order { OrderId = 56, CustomerName = "Peggy", IsArchived = false });
    orders.Add(new Order { OrderId = 60, CustomerName = "Carol", IsArchived = false });
    orders.Add(new Order { OrderId = 62, CustomerName = "Bruce", IsArchived = false });
    return orders;
}

private async Task<List<Order>> GetArchivedOrdersAsync()
{
    var orders = new List<Order>();
    await Task.Delay(5000);
    orders.Add(new Order { OrderId = 3, CustomerName ="Howard", IsArchived = true });
    orders.Add(new Order { OrderId = 18, CustomerName= "Steve", IsArchived = true });
    orders.Add(new Order { OrderId = 19, CustomerName = "Peter", IsArchived = true });
    orders.Add(new Order { OrderId = 21, CustomerName = "Mary", IsArchived = true });
    orders.Add(new Order { OrderId = 25, CustomerName= "Gwen", IsArchived = true });
    orders.Add(new Order { OrderId = 34, CustomerName = "Harry", IsArchived = true });
    orders.Add(new Order { OrderId = 36, CustomerName= "Bob", IsArchived = true });
    orders.Add(new Order { OrderId = 49, CustomerName = "Bob", IsArchived = true });
    return orders;
}
  1. 现在我们需要创建一个同步 ProcessOrders 方法,该方法组合两个订单列表并使用完整数据集更新 Orders 属性:
private void ProcessOrders(List<Order> currentOrders,List<Order> archivedOrders)
{
    List<Order> allOrders = new(currentOrders);
    allOrders.AddRange(archivedOrders);
    Orders = new ObservableCollection<Order>(allOrders);
}
  1. 构建MainViewModel 类的最后一步是最重要的。 将以下实现添加到 LoadOrderDataAsync 方法:
private async Task LoadOrderDataAsync()
{
    Task<List<Order>> currentOrdersTask =GetCurrentOrdersAsync();
    Task<List<Order>> archivedOrdersTask = GetArchivedOrdersAsync();
    
    List<Order>[] results = await Task.WhenAll(new
    	Task<List<Order>>[] {currentOrdersTask, archivedOrdersTask})
        .ConfigureAwait(false);
    
    ProcessOrders(results[0], results[1]);
}

此方法调用 GetCurrentOrdersAsyncGetArchivedOrdersAsync 并捕获 Task<List<Order>> 变量中的每个值。 您可以简单地等待每个调用并将返回的订单存储在 List<Order> 变量中。 但是,这意味着在第一个方法完成之前,第二个方法不会开始执行。 通过等待 Task.WhenAll,这些方法可以在后台线程上并行执行。

如果您的方法都返回相同的数据类型,则可以在返回类型的数组中捕获 Task.WhenAll 的结果。 在我们的例子中,我们接收 List<Order> 数组中的两个订单列表,并将这两个数组值传递给 ProcessOrders

  1. 现在,让我们继续讨论 MainWindow.xaml.cs 代码隐藏文件。 在调用InitializeComponent之后,在构造函数中添加以下代码来设置MainWindowDataContext
public MainWindow()
{
    InitializeComponent();
    var vm = new MainViewModel();
    DataContext = vm;
}

DataContextMainWindowXAML 中所有 Binding 引用的源。 我们将在下一步中为 UI 创建 XAML。

  1. 最后一个要更新的文件是MainWindow.xaml。 打开 XAML 文件并首先向 Grid 添加两行。 第一行将包含另一个包含 ButtonTextBoxGrid。第二行将包含 ListView 以显示订单列表。 我们稍后将为订单创建一个模板:
<Grid>
    <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid Grid.Row="0" Margin="4">
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <Button Content="Load Data" Grid.Column="0" Margin="2" Width="200"
    Command="{Binding Path=LoadOrderData Command}"/>
    <TextBox Grid.Column="1" Margin="2"/>
    
    </Grid>
    <ListView Grid.Row="1" ItemsSource="{Binding Path=Orders}" Margin="4">
    </ListView>
    </Grid>
</Grid>

我在 XAML 标记中突出显示了两个数据绑定实例。 ButtonCommand 绑定到 LoadOrderDataCommand 属性,ListViewItemsSource 绑定到 Orders 属性。 设置 ItemsSource 将使 Order 类的属性可供 ListView.ItemTemplate 的成员使用。

  1. 接下来我们将 ItemTemplate 添加到 ListView 中。 在 ItemTemplate 中定义 DataTemplate 定义了 ListView 中每个项目的结构:
<TextBox IsReadOnly="True"
    Width="200"
    Text="{Binding  Path=OrderId}" Margin="2"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
    <TextBlock Text="Customer:" Margin="2,2,0,2" Width="100"/>
    <TextBox IsReadOnly="True" Width="200" Text="{Binding Path=CustomerName}" Margin="2"/>
    </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Archived:" Margin="2,2,0,2" Width="100"/>
            <TextBox IsReadOnly="True"  Width="200"Text="{Binding Path=IsArchived}"  Margin="2"/>
     	 </StackPanel>
    </StackPanel>
    </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

每个 Order 实例将呈现为一个 StackPanel,其中包含三个水平对齐的 StackPanel 元素,显示 OrderIdCustomerNameIsArchived 数据绑定属性的标签和值。

  1. 我们已准备好运行该应用程序并查看其工作原理。 程序启动后,单击“加载数据”按钮。 将数据加载到 ListView 大约需要 5 秒。 等待期间,尝试在“加载数据”按钮右侧的框中输入一些文本。 您可以看到,由于 async/awaitTask.WhenAll 方法,UI 仍然响应用户输入。数据加载完成后,您应该在可滚动列表中看到包含 12 个订单的列表:

图 4.2 – 在 AsyncWithWpf 应用程序中查看订单列表

image

在实际的生产应用程序中,两个异步方法的实现将被服务调用所取代,以从数据库或 Web 服务中获取数据。 无论需要多长时间才能返回
并填充数据,UI 的其他部分将保持对用户输入的响应。 您想要进行的一项更改是向 UI 添加一个指示器,以通知用户正在加载数据。 您还应该在数据加载过程处于活动状态时禁用“加载数据”按钮,以防止多次调用 LoadOrderDataAsync

使用线程池

还有其他方法可以在 .NET 应用程序中使用 ThreadPool 线程。 让我们讨论一种情况,您想要完成与上一示例中使用 async 和 wait 获得的相同结果,但获取订单数据的方法未标记为异步。 一种选择是将方法更新为异步。 如果您无法控制更改该代码,您还有其他一些选项可用。

ThreadPool 类有一个名为 QueueUserWorkItem 的方法。 此方法接受一个方法来调用并将其排队以在线程池上执行。 我们可以像这样在我们的项目中使用它:

ThreadPool.QueueUserWorkItem(GetCurrentOrders);

使用此方法存在一些问题。 主要问题是没有返回值来从方法调用中获取订单列表。 您可以使用一些更新共享线程安全集合(例如 BlockingCollection)的包装方法来解决此问题。 这不是一个很好的设计,而且还有更好的选择。

在引入 TPL 之前,QueueUserWorkItem 方法更常用。在当今基于任务的世界中,您可以使用 Task.Run 将同步方法作为异步方法执行。 让我们更新 WPF 项目以使用 Task.Run

  1. 使用Task.Run唯一需要修改的文件是MainViewModel。 首先将 GetCurrentOrdersAsyncGetArchivedOrdersAsync 更新为不再是异步方法。 它们还应该重命名为 GetCurrentOrdersGetArchivedOrders,以便消费者知道它们不是异步方法:
private List<Order> GetCurrentOrders()
{
    var orders = new List<Order>();
    Thread.Sleep(4000);
    orders.Add(new Order { OrderId = 55, CustomerName
        = "Tony", IsArchived = false });
    orders.Add(new Order { OrderId = 56, CustomerName
        = "Peggy", IsArchived = false });
    orders.Add(new Order { OrderId = 60, CustomerName
        = "Carol", IsArchived = false });
    orders.Add(new Order { OrderId = 62, CustomerName
        = "Bruce", IsArchived = false });
    return orders;
}

private List<Order> GetArchivedOrders()
{
    var orders = new List<Order>();
    Thread.Sleep(5000);
    orders.Add(new Order { OrderId = 3, CustomerName =
        "Howard", IsArchived = true });
    orders.Add(new Order { OrderId = 18, CustomerName
        = "Steve", IsArchived = true });
    orders.Add(new Order { OrderId = 19, CustomerName
        = "Peter", IsArchived = true });
    orders.Add(new Order { OrderId = 21, CustomerName
        = "Mary", IsArchived = true });
    orders.Add(new Order { OrderId = 25, CustomerName
        = "Gwen", IsArchived = true });
    orders.Add(new Order { OrderId = 34, CustomerName
        = "Harry", IsArchived = true });
    orders.Add(new Order { OrderId = 36, CustomerName
        = "Bob", IsArchived = true });
    orders.Add(new Order { OrderId = 49, CustomerName
        = "Bob", IsArchived = true });
    return orders;
}

更改很小,我在前面的源代码中突出显示了它们。 async 修饰符已从方法声明中删除,方法已重命名并且不再返回任务,并且每个方法中的 Task.Delay 已更新为 Thread.Sleep

  1. 接下来,我们将更新 LoadOrderDataAsync 方法以使用 Task.Run 调用同步方法:
private async Task LoadOrderDataAsync()
{
    Task<List<Order>> currentOrdersTask =
        Task.Run(GetCurrentOrders);
    Task<List<Order>> archivedOrdersTask =
        Task.Run(GetArchivedOrders);
    List<Order>[] results = await Task.WhenAll(new
                                               Task<List<Order>>[] {
                                                   currentOrdersTask, archivedOrdersTask
                                               }).ConfigureAwait(false);
    ProcessOrders(results[0], results[1]);
}

无需进行其他更改。 Task.Run 将返回相同的 Task<List<Order>> 类型,该类型仍然可以与 Task.WhenAll 一起使用以等待其完成。

  1. 运行程序,它应该像以前一样工作。 加载订单数据时,UI 保持响应。

这是开始将 asyncwait 合并到现有代码中的绝佳方法,但在向应用程序添加线程时始终要小心。 在此应用程序中,被调用的两个方法不访问任何共享数据。 所以,没有必要考虑线程安全。 如果这些方法正在更新订单的私有集合,您将需要引入锁定机制或使用订单的线程安全集合。

在我们继续讨论 UI 线程之前,还需要讨论另一种 Task 方法。 Task.Factory.StartNew 方法的用法与Task.Run 类似。 事实上,您可以以同样的方式使用它们。 此代码使用 Task.Run 获取具有当前订单的任务:

Task<List<Order>> currentOrdersTask = Task.Run(GetCurrentOrders);

此代码与 Task.Factory.StartNew 执行相同的操作:

Task<List<Order>> currentOrdersTask = Task.Factory.StartNew(GetCurrentOrders);

在这种情况下,您应该使用Task.Run。 这是一种较新的方法,只是一种旨在简化最常见用例的快捷方式。 Task.Factory.StartNew 方法有一些针对特定用途的额外重载。 此示例使用 StartNew 调用 GetCurrentOrders 并带有一些可选参数:

Task<List<Order>> currentOrdersTask =
    Task.Factory.StartNew(GetCurrentOrders,
                          CancellationToken.None,
                          TaskCreationOptions.AttachedToParent,
                          TaskScheduler.Default);

我们在这里提供的有趣选项是 TaskCreationOptions.AttachedToParent。 它的作用是将调用方法的任务完成情况链接到子方法 GetCurrentOrders 的任务完成情况。 默认行为是取消链接它们的完成。 有关可用重载及其用途的完整列表,您可以在此处查看 Microsoft 文档:https://docs.microsoft.com/dotnet/api/system.threading.tasks.taskfactory.startnew.

.NET 团队的 Stephen Toub 有一篇博客文章,其中讨论了 Task.RunTask.Factory.StartNew 以及您可能想要选择每个选项的原因。 您可以在此处阅读他在 .NET 并行编程博客上的文章:https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/。

更新UI线程无异常

在 .NET 应用程序中使用托管线程时,开发人员必须学会避免许多陷阱。 开发人员常犯的错误之一是编写从非 UI 线程更新 Windows 应用程序中的 UI 控件的代码。 编译器不会检测到这种错误。 开发人员将收到一个运行时错误,指示无法在另一个线程上修改在主线程上创建的控件。

那么,如何避免这些运行时错误呢? 避免它们的最佳方法是根本不从后台线程更新 UI 控件。 WPF 有助于避免 MVVM 模式和数据绑定的问题。 .NET 会自动将绑定更新编组到 UI 线程。 您可以从后台线程安全地更新 ViewModel 类中的属性,而不会在运行时引起错误。如果您直接在代码中更新 UI 控件,无论是在 WinForms 应用程序中还是在 WPF 控件的代码隐藏文件中,您可以使用 Invoke 调用将执行推至主线程。 WinForms 和 WPF 的实现略有不同。 让我们从一个 WPF 示例开始。 如果您有一个方法在后台线程上执行某些工作,并且需要更新 WPF 窗口上 TextBoxText 属性,您可以将代码包装在一个操作中:

Application.Current.Dispatcher.Invoke(new Action(() => {
    usernameTextBox.Text = "John Doe";
}));

Dispatcher.Invoke 会将执行推送到主线程。 请记住,如果主线程正忙于其他工作,您的后台线程将在这里等待此操作完成。 如果您的后台工作人员想要解雇并忘记此操作,您可以使用 Dispatcher.BeginInvoke 代替。

假设我们想要更新 usernameTextBox,但这一次,我们正在使用 WinForms 项目。 通过使用 FormUserControl 执行代码可以完成相同的调用。 此示例是一个带有两个按钮的 WinForms 应用程序。 单击一个按钮将调用 UpdateUsername 方法。 另一个按钮将调用 Task.Run(UpdateUsername),将其放在后台线程上。 要确定是否需要 Invoke 来访问主线程,请检查 Boolean InvokeRequired 只读属性。 如果线程池选择在主线程上运行任务,则可能不需要:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void btnRunInBackground_Click(object sender,
                                          EventArgs e)
    {
        Task.Run(UpdateUsername);
    }
    private void btnRunOnMainThread_Click(object sender,
                                          EventArgs e)
    {
        UpdateUsername();
    }
    private void UpdateUsername()
    {
        var updateAction = new Action(() =>
                                      {
                                          usernameTextBox.Text = "John Doe";
                                      });
        if (this.InvokeRequired)
        {
            this.Invoke(updateAction);
        }
        else
        {
            updateAction();
        }
    }
}

无论单击哪个按钮,usernameTextBox 都会成功显示 John Doe 的名字:

图 4.3 – 更新 WinForms 表单上的控件

image

与WPF一样,如果后台代码不需要等待主线程更新完成,WinForms有一个BeginInvoke方法。 BeginInvoke 还可以接受 EndInvoke 委托,该委托将在主线程调用完成时接收回调。

标签:Task,用户界面,编程,Order,线程,IsArchived,new,orders
From: https://www.cnblogs.com/King2019Blog/p/17763925.html

相关文章

  • SimpleDateFormat线程安全性
    SimpleDateFormat线程安全性0结论SimpleDateFormat是线程不安全的。在JDK中关于SimpleDateFormat有这样一段描述:Dateformatsarenotsynchronized.Itisrecommendedtocreateseparateformatinstancesforeachthread.Ifmultiplethreadsaccessaformatconcurr......
  • 并行编程-1.托管线程概念
    .NET线程基础知识是时候开始学习C#和.NET中的线程基础知识了。我们将介绍.NET6中可用的托管线程概念,但其中许多功能从一开始就是.NET的一部分。System.Threading命名空间从.NETFramework1.0开始可用。在随后的20年中,为开发人员添加了许多有用的功能。为了在......
  • 多线程逆向
    一.资料检索以及归纳XDBG调试时默认是只运行下断调试的线程其它线程处于暂停挂起状态属于单线程调试。打开线程窗口可以查看线程挂起计数(+号-号快捷键可以挂起恢复线程)双击线程可进入选择线程,如果要调试指定线程的话我的理解是应该在线程代码中下断(线程的各种系统CALL),线程会自......
  • 晨控CK-GW06系列网关与汇川可编程控制器MOSBUSTCP通讯手册
    晨控CK-GW06系列网关与汇川可编程控制器MOSBUSTCP通讯手册晨控CK-GW06系列是支持标准工业通讯协议MODBUSTCP的网关控制器,方便用户集成到PLC等控制系统中。本控制器提供了网络POE供电和直流电源供电两种方式,确保用户在使用无POE供电功能的交换机时可采用外接电源供电;系统还集......
  • [转载]jmeter - 如何在指定数量的线程失败后停止 Jmeter 测试
     原地址https://www.coder.work/article/7090277  我有一个事务Controller,它在我的Jmeter测试计划中有一个http请求。交易名称和url来自CSV文件。最后,总执行分为5个不同的事务。测试计划:测试计划-线程组-用户定义的变量总样本执行量为8000-10000。现在......
  • Nacos 实现动态化线程池,真香!
    在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数。在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高。一种解决办法就是,将线程池的配......
  • C语言 - 使用_beginthreadex()创建线程
    经过了解才知道,C++03之前,用的创建线程都是CreateThread与_beginthreadex。使用这个两个函数进行创建线程。然后C++11之后,就出现了新的线程函数thread,当然,这个创建线程比较方便!经过两三天的纠结,最终决定深入研究_beginthreadex此方式创建线程,具体为什么我也说不清楚,看到网上很多......
  • Julia中的异步编程
    在本章中,我们将学习Julia异步编程的基础知识,我们将了解:taskschannelsTasks创建任务从技术上讲,Julia中的任务是symmetricco-routine(对称协同例程)。更通俗地说,task是一项计算工作,可以在将来的某个时刻开始安排,并且可以中断和恢复。要创建任务,我们首先需要创建一个函数来表示......
  • socket网络编程
    Socket网络编程一、计算机网络概述1、IP地址的概念IP地址就是标识网络中设备的一个地址,好比现实生活中的家庭住址。网络设备的效果图:2、IP地址的表现形式说明:IP地址分为两类:IPv4和IPv6IPv4是目前使用的IP地址IPv6是未来使用的IP地址IPv4是由点分十进制组成IPv6是......
  • Shell(四):awk编程
    1、awk简介awk因三位缔造者的名字而命令(Aho、Weinberger和Kernighan),是一种能够对结构化数据进行操作,并产生格式化报表的编程语言。awk功能与sed相似,都是用来进行文本处理的,awk语言可以从文件或字符串中基于指定规则浏览和抽取信息,在抽取信息的基础上,才能进行其他文本......