【问题标题】:Replace 'BackgroundWorker' With 'Thread '将 'BackgroundWorker' 替换为 'Thread'
【发布时间】:2021-03-10 04:46:28
【问题描述】:

我想用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;
    }));
}

但运行这些代码后,用户界面在睡眠期间挂起。
我的目的正确的代码是什么?

【问题讨论】:

  • Thread_Method() 实际上在做什么?您是否以任何方式访问 UI 线程?调用()?
  • 它从网站读取数据并将它们写入文本文件。我在 UI 线程中也有一个日志文本框,并在 Thread_Method() 期间在其中写入日志
  • 是的,这就是您需要展示的部分。如果您拨打Invoke(),请尝试使用BeginInvoke()。但是,既然你显然有 I/O 绑定的tasks,为什么不使用 async/await 模式呢?或者运行异步任务并使用 IProgress<T> 委托来更新 UI。
  • A backgroundWorker 还使用另一个线程进行 DoWork() 回调。那你为什么要换?听起来您当前的实现使用 BackgroundWorker 并且您的 UI 冻结,现在您喜欢使用显式线程来规避此问题。但根本原因是相同的,因此您的线程也会冻结 UI。
  • 使用async / await看看this answer。请注意,您必须致电 await Task.Delay() 而不是 Thread.Sleep()

标签: c# multithreading winforms visual-studio-2015 backgroundworker


【解决方案1】:

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

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

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

如果您不熟悉 async-await:this interview with Eric Lippert 确实帮助我了解了使用 async-await 时会发生什么。在中间某处搜索 async-await。

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

使您的 ThreadMethod 异步:

  • 声明它是异步的

  • 而不是TResults返回Task&lt;TResult&gt;;而不是void 返回Task

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

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

    公共异步任务 FetchCustomerAddress(int customerId) { // 从数据库中获取客户地址: 使用 (var dbContext = new OrderDbContext(...)) { 返回等待 dbContext.Customers .Where(客户 => customer.Id == customerId) .Select(客户 => 新地址 { 名称 = 客户名称, 街道 = 客户.街道, ... // 等等 }) .FirstOrDefaultAsync(); } }

    公共异步任务 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 线程响应。在较旧的应用程序中,您将为此使用 BackgroundWorker,在较新的应用程序中,您使用 Task.StartNew

例如,您有一个按钮和一个菜单项,它们都会开始一些繁重的计算。就像使用 backgroundworker 时你想显示一些进度一样。在进行计算时,菜单项和按钮都需要禁用。

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 方法中完成,例如读取数据库信息、写入文件等。backgroundWorker 无法取消写入文件。

为此,我们有CancellationTokenSource

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;
        }
    }
}
    

【讨论】:

    【解决方案2】:

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

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

    Task.run

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-30
      • 1970-01-01
      • 2011-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-04
      相关资源
      最近更新 更多