将"后台工作者"替换为"线程"



我想用Thread.
替换winform 应用程序中的BackgroundWorker/目标是在 UI 线程以外的新线程中执行作业并防止程序在运行期间挂起.
所以我这样做了:

private void radBtn_start_Click(object sender, EventArgs e)
{
try
{
string thread_name = "trd_" + rnd.Next(99000, 10000000).ToString();
Thread thread = new Thread(new ThreadStart(Thread_Method));
thread.Name = thread_name;
thread.Start();
}
catch (System.Exception ex)
{
MessageBox.Show("Error in radBtn_start_Click() Is : " + ex.ToString());
}
}

public void Thread_Method()
{
...Some Jobs
Thread.Sleep(20000);
...Some Jobs After Delay
Thread.Sleep(20000);
...Some Jobs After Delay
this.Invoke(new MethodInvoker(delegate
{
radTextBoxControl1.Text += DateTime.Now.ToString() + " : We are at end of search( " + radDropDownList1.SelectedItem.Tag + " ) = -1" + Environment.NewLine;
}));
}

但是运行这些代码后,UI 在睡眠期间挂起.
适合我的目的的正确代码是什么?

您不必创建新的线程,您的进程已经有一个线程池焦急地等待为您做某事

通常,线程池中的线程在使用 async-await 时使用。但是,您也可以将它们用于繁重的计算

我的建议是让你的thread_method异步。这样做的好处是,每当thread_method必须闲置等待另一个进程完成时,例如将数据写入文件、从数据库获取项目或从 Internet 读取信息,线程池都可以执行其他任务。

如果你不熟悉 async-await:这次对 Eric Lippert 的采访真的帮助我理解了当你使用 async-await 时会发生什么。在中间的某个地方搜索异步等待。

async-await 的一个好处是,执行线程与 UI 线程具有相同的"上下文",因此该线程可以访问 UI 元素。无需检查 InvokeRequired 或调用 Invoke。

要使 ThreadMethod 异步,请执行以下操作:

  • 声明它是异步的

  • 而不是返回TResultsTask<TResult>;而不是void返回Task

  • 唯一的例外:异步事件处理程序返回 void

  • 每当调用具有异步版本的其他方法时,请调用此异步版本,在需要异步任务的结果时开始等待。

    public async Task FetchCustomerAddress(int customerId) { 从数据库中获取客户地址: using (var dbContext = new OrderDbContext(...)) { return await dbContext.Customers .其中(客户 => 客户。Id == 客户 ID) .选择(客户 => 新地址 { 名称 = 客户。名字 街道=客户。街 。。。//等 }) .FirstOrDefaultAsync(); } }

    public async Task CreateCustomerOrder( int customerId, IEnumerable orderLines) { 开始读取客户地址 var taskReadCustomerAddress = this.FetchCustomerAddress(customerId);

    // meanwhile create the order
    CustomerOrder order = new CustomerOrder();
    foreach (var orderLine in orderLines)
    {
    order.OrderLines.Add(orderLine);
    }
    order.CalculateTotal();
    // now you need the address of the customer: await:
    Address customerAddress = await taskReadCustomerAddress;
    order.Address = customerAddress;
    return order;
    

    }

有时,您不必闲置地等待另一个进程完成,但您需要执行一些繁重的计算,并且仍然保持 UI 线程响应。在较旧的应用程序中,您将使用后台工作者,在较新的应用程序中,您可以使用Task.StartNew

例如,您有一个按钮和一个菜单项,它们都将启动一些繁重的计算。就像使用后台工作者一样,您希望显示一些进度。在进行计算时,菜单项和按钮都需要禁用。

public async Task PrintCustomerOrdersAsync(
ICollection<CustomerOrderInformation> customerOrders)
{
// while creating the customer orders: disable the button and the menu items
this.buttonPrintOrders.Enabled = false;
this.menuItemCreateOrderLines.Enabled = false;
// show the progress bar
this.ProgressBarCalculating.MinValue = 0;
this.ProgressBarCalculating.MaxValue = customers.Count;
this.ProgressBarCalculating.Value = 0;
this.ProgressBarCalculating.Visible = true;
List<Task<PrintJob>> printJobs = new List<Task<PrintJob>>();
foreach (CustomerOrderInformation orderInformation in customerOrders)
{
// instead of BackGroundworker raise event, you can access the UI items yourself
CustomerOrder order = this.CreateCustomerOrder(orderInformation.CustomerId,
orderInformation.OrderLines);
this.ProgressBarCalculating.Value +=1;
// print the Order, do not await until printing finished, create next order
printJobs.Add(this.Print(order));
}
// all orders created and sent to the printer. await until all print jobs complete:
await Task.WhenAll(printJobs);
// cleanup:
this.buttonPrintOrders.Enabled = true;
this.menuItemCreateOrderLines.Enabled = true;
this.ProgressBarCalculating.Visible = false;
}

顺便说一句:在适当的设计中,您将启用/禁用项目与实际处理分开:

public async Task PrintCustomerOrdersAsync(ICollection<CustomerOrderInformation> customerOrders)
{
this.ShowBusyPrintingOrders(customerOrders.Count);
await this.PrintOrdersAsync(customerOrders);
this.HideBusyPrintingOrders();
}

现在要在按下按钮时开始打印订单,有两种可能性:

  • 如果进程主要在等待其他进程:异步事件处理程序
  • 如果计算非常繁重(超过一秒?):启动一个执行计算的任务

无需繁重的计算:

// async event handler has void return value!
private async void ButtonPrintOrdersClickedAsync(object sender, ...)
{
var orderInformations = this.GetOrderInformations();
await PrintCustomerOrdersAsync(orderInformations);
}

因为我没有其他有用的事情要做,所以我立即等待

繁重的计算:启动一个单独的任务:

private async Task ButtonCalculateClickedAsync(object sender, ...)
{
var calculationTask = Task.Run(() => this.DoHeavyCalculations(this.textBox1.Text);
// because you didn't await, you are free to do something else,
// for instance show progress:
while (!calculationTask.Complete)
{
// await one second; UI is responsive!
await Task.Delay(TimeSpan.FromSeconds(1));
this.ProgressBar.Value += 1;
}
}

请注意:使用这些方法,您无法停止该过程。因此,如果操作员想在您仍在打印时关闭应用程序,您将遇到麻烦。

就像后台线程一样,每个支持取消的方法都应定期检查是否请求取消。优点是,此检查也在支持取消的 .NET 方法中完成,例如读取数据库信息、写入文件等。后台工作线程无法取消对文件的写入。

为此,我们有 取消令牌源

private CancellationTokenSource cancellationTokenSource;
private Task taskPrintOrders;
public async Task PrintCustomerOrdersAsync(ICollection<CustomerOrderInformation> customerOrders)
{
this.ShowBusyPrintingOrders(customerOrders.Count);
using (this.cancellactionTokenSource = new CancellationTokenSource())
{
taskPrintOrders = this.PrintOrdersAsync(customerOrders, this.cancellationTokenSource.Token);
await taskPrintOrders;
this.HideBusyPrintingOrders();
}
private void CancelPrinting()
{
this.cancellationTokenSource?.Cancel();
}

如果要取消并等待完成,例如在关闭表单时:

private bool TaskStillRunning => this.TaskPrinting != null && !this.TaskPrinting.Complete;
private async void OnFormClosing(object sender, ...)
{
if (this.TaskStillRunning)
{ 
bool canClose = this.AskIfCanClose();
if (!canClose)
eventArgs.Cancel = true;
else
{ 
// continue closing: stop the task, and wait until stopped
this.CancelPrinting();
await this.taskPrintOrders;
}
}
}

这将在单独的线程中工作,而不会挂起您的 UI。
使用新线程

new Thread(delegate()
{ 
Thread_Method();
}).Start();

Task.run

Task.Run(() =>  
{  
Thread_Method();
}); 

最新更新