将 'BackgroundWorker' 替换为 'Thread '
Replace 'BackgroundWorker' With 'Thread '
我想用 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 在睡眠期间挂起。
适合我的目的的正确代码是什么?
这将在单独的线程中工作,而不会挂起您的 UI。
使用新线程
new Thread(delegate()
{
Thread_Method();
}).Start();
或Task.run
Task.Run(() =>
{
Thread_Method();
});
您不必创建新线程,您的进程已经有一个线程池正焦急地等待着为您做某事
一般使用async-await的时候都会用到线程池中的线程。但是,您也可以将它们用于繁重的计算
我的建议是使您的 thread_method 异步。这样做的好处是,每当您的 thread_method 必须空闲地等待另一个进程完成时,例如将数据写入文件、从数据库中获取项目或从 Internet 读取信息,该线程都可以用于该线程池执行其他任务。
如果您不熟悉 async-await:this interview with Eric Lippert 确实帮助我理解了使用 async-await 时会发生什么。在中间某处搜索 async-await。
async-await 的优点之一是执行线程与 UI-thread 具有相同的“上下文”,因此该线程可以访问 UI-elements。无需检查 InvokeRequired 或调用 Invoke。
要使您的 ThreadMethod 异步:
声明为异步
代替TResults
returnTask<TResult>
;而不是 void
return Task
唯一例外:异步事件处理程序return无效
每当你调用其他有异步版本的方法时,调用这个异步版本,当你需要异步任务的结果时开始等待。
public 异步任务 FetchCustomerAddress(int customerId)
{
// 从数据库中获取客户地址:
使用 (var dbContext = new OrderDbContext(...))
{
return 等待 dbContext.Customers
.Where(客户 => customer.Id == customerId)
.Select(客户=>新地址
{
姓名=customer.Name,
街道 = customer.Street,
... // ETC
})
.FirstOrDefaultAsync();
}
}
public 异步任务 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;
}
}
}
我想用 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 在睡眠期间挂起。
适合我的目的的正确代码是什么?
这将在单独的线程中工作,而不会挂起您的 UI。
使用新线程
new Thread(delegate()
{
Thread_Method();
}).Start();
或Task.run
Task.Run(() =>
{
Thread_Method();
});
您不必创建新线程,您的进程已经有一个线程池正焦急地等待着为您做某事
一般使用async-await的时候都会用到线程池中的线程。但是,您也可以将它们用于繁重的计算
我的建议是使您的 thread_method 异步。这样做的好处是,每当您的 thread_method 必须空闲地等待另一个进程完成时,例如将数据写入文件、从数据库中获取项目或从 Internet 读取信息,该线程都可以用于该线程池执行其他任务。
如果您不熟悉 async-await:this interview with Eric Lippert 确实帮助我理解了使用 async-await 时会发生什么。在中间某处搜索 async-await。
async-await 的优点之一是执行线程与 UI-thread 具有相同的“上下文”,因此该线程可以访问 UI-elements。无需检查 InvokeRequired 或调用 Invoke。
要使您的 ThreadMethod 异步:
声明为异步
代替
TResults
returnTask<TResult>
;而不是void
returnTask
唯一例外:异步事件处理程序return无效
每当你调用其他有异步版本的方法时,调用这个异步版本,当你需要异步任务的结果时开始等待。
public 异步任务 FetchCustomerAddress(int customerId) { // 从数据库中获取客户地址: 使用 (var dbContext = new OrderDbContext(...)) { return 等待 dbContext.Customers .Where(客户 => customer.Id == customerId) .Select(客户=>新地址 { 姓名=customer.Name, 街道 = customer.Street, ... // ETC }) .FirstOrDefaultAsync(); } }
public 异步任务 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;
}
}
}