You don't have to create a new Thread, your process already has a pool of threads anxiously waiting to do something for you
Usually the threads in the thread pool are used when you use async-await. However, you can also use them for heavy calculations
My advice is to make your thread_method async. This has the advantage, that whenever your thread_method has to wait idly for another process to finish, like writing data to a file, fetching items from a database, or reading information from the internet, the thread is available for the thread pool to do other tasks.
If you are not familiar with async-await: this interview with Eric Lippert really helped me to understand what happens when you use async-await. Search somewhere in the middle for async-await.
One of the nice things about async-await, is that the executing thread has the same "context" as the UI-thread, so this thread can access UI-elements. No need to check for InvokeRequired or to call Invoke.
To make your ThreadMethod async:
declare it async
instead of TResults
return Task<TResult>
; instead of void
return Task
only exception: async event handlers return void
whenever you call other methods that have an async version, call this async version, start awaiting when you need the results of the async task.
public async Task FetchCustomerAddress(int customerId)
{
// fetch the customer address from the database:
using (var dbContext = new OrderDbContext(...))
{
return await dbContext.Customers
.Where(customer => customer.Id == customerId)
.Select(customer => new Address
{
Name = customer.Name,
Street = customer.Street,
... // etc
})
.FirstOrDefaultAsync();
}
}
public async Task CreateCustomerOrder(
int customerId, IEnumerable orderLines)
{
// start reading the customer Address
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;
}
Sometimes you don't have to wait idly for another process to finish, but you need to do some heavy calculations, and still keep your UI-thread responsive. In older applications you would use the BackgroundWorker for this, in newer applications you use Task.StartNew
For instance, you have a button, and a menu item that both will start some heavy calculations. Just like when using the backgroundworker you want to show some progress. While doing the calculations, both the menu item and the button need to be disable.
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;
}
By the way: in a proper design, you would separate the enabling / disabling the items from the actual processing:
public async Task PrintCustomerOrdersAsync(ICollection<CustomerOrderInformation> customerOrders)
{
this.ShowBusyPrintingOrders(customerOrders.Count);
await this.PrintOrdersAsync(customerOrders);
this.HideBusyPrintingOrders();
}
Now to start printing the orders when a button is pressed, there are two possibilities:
- If the process is mostly waiting for others: async event handler
- If there are really heavy calculations (longer than a second?): start a task that does the calculations
No heavy calculations:
// async event handler has void return value!
private async void ButtonPrintOrdersClickedAsync(object sender, ...)
{
var orderInformations = this.GetOrderInformations();
await PrintCustomerOrdersAsync(orderInformations);
}
Because I don't have anything other useful to do, I await immediately
Heavy calculations: start a separate task:
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;
}
}
Be aware: using these methods, you can't stop the process. So you are in trouble if the operator wants to close the application while you are still printing.
Just like your background thread, every method that supports cancellation should regularly check if cancellation is requested. The advantage is, that this checking is also done in the .NET methods that support cancellation, like reading database information, writing a file, etc. The backgroundWorker couldn't cancel writing to a file.
For this we have the 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();
}
If you want to cancel and wait until finished, for instance when closing the form:
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;
}
}
}