0

I want to replace BackgroundWorker in my winform application with a Thread.
The goal is do the the jobs in a new thread other than UI-thread & prevent program hang during run.
So i did this :

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

But after running these codes UI hangs during sleep.
What is the correct codes for my purpose?

SilverLight
  • 19,668
  • 65
  • 192
  • 300
  • 1
    What is `Thread_Method()` actually doing? Are you accessing the UI Thread in any way? Invoking()? – Jimi Nov 27 '20 at 11:42
  • It reads data from a web site and write them on a text file. I also have a log text box in UI thread and write logs in that during `Thread_Method()` – SilverLight Nov 27 '20 at 11:44
  • Yes, well, that's the part that you need to show. If you're calling `Invoke()`, try `BeginInvoke()` instead. BUT, since you, apparently, have I/O-bound *tasks*, why not use the async/await pattern instead? Or run an async Task and use an [IProgress](https://learn.microsoft.com/en-us/dotnet/api/system.progress-1.onreport) delegate to update the UI. – Jimi Nov 27 '20 at 11:46
  • 2
    A `backgroundWorker` also uses another thread for the `DoWork()` callback. So why do you want to switch? It sounds like your current implemenation uses BackgroundWorker and your UI freezes and now you like to use an explicit thread to circumvent this problem. But the root cause is the same and so your thread also freezes the UI. – Oliver Nov 27 '20 at 11:48
  • Show me how can i take advantage of async/await pattern. – SilverLight Nov 27 '20 at 11:48
  • As you can see [here](https://stackoverflow.com/a/55097565/1838048) `BackgroundWorker` doesn't freeze your UI if used correctly. – Oliver Nov 27 '20 at 11:50
  • 1
    For using `async / await`take a look at [this answer](https://stackoverflow.com/a/51664345/1838048). Be aware that you have to call `await Task.Delay()` instead of `Thread.Sleep()`. – Oliver Nov 27 '20 at 11:53
  • Thanks for the help & links. – SilverLight Nov 27 '20 at 12:09
  • [Here](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker/64620920#64620920) is a `BackgroundWorker` vs `Task.Run` + async/await comparison, if you are interested. – Theodor Zoulias Nov 28 '20 at 00:54

2 Answers2

0

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;
        }
    }
}
    
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
-2

This will work in separate thread without hanging your UI.
Use new Thread

new Thread(delegate()
{ 
    Thread_Method();

}).Start();

or Task.run

Task.Run(() =>  
{  
    Thread_Method();
}); 
Sorry IwontTell
  • 466
  • 10
  • 29