async/await in winforms

Costas

Administrator
Staff member
Is acceptable to use async void to event handlers.
  • The event handler return void.
  • There is no calling code to await the event handler.
In general should be aware that async void methods cannot be awaited, and any exceptions thrown within them cannot be caught by the calling code.

C#:
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await Task.Delay(1000);
        await SomeAsyncMethod1();
        await SomeAsyncMethod2();

        // This is safe because we are back on the UI thread after await
        label1.Text = "Updated from async method";
 
        // use BeginInvoke to update the UI from a #background thread#. It ensures that the update is #marshaled back# to the UI thread.
        // label1.BeginInvoke(new Action(() => label1.Text = "Updated from async method"));

        MessageBox.Show("Operation completed successfully!");
    }
    catch (Exception ex)
    {
        // Handle exceptions that occur during the async operation
        MessageBox.Show($"An error occurred: {ex.Message}");
    }
}



IProgress<T> Interface

Is an interface to report progress in an asynchronous operation. Ensures that the progress reporting mechanism is thread-safe and that the consumer of the progress updates can safely update the UI or other state based on the progress.

Using IProgress<T> is generally the better approach in modern C# development for handling UI updates from background tasks. It simplifies the code, integrates with the async/await pattern, and is less prone to errors.

Using BeginInvoke can still be valid for specific scenarios where you need non-blocking calls and have existing codebases that utilize this method. However, for new projects, IProgress<T> is recommended for its simplicity and effectiveness.

C#:
private async void buttonStart_Click(object sender, EventArgs e)
{
    var progress = new Progress<int>(value =>
    {
        progressBar.Value = value;
    });

    string comboValue = comboProfile.Text;

    await Task.Run(() => DoWork(comboValue, progress));
    // Use Task.Factory.StartNew for .NET Framework 4.0 return await Task.Factory.StartNew(() => DoWork(progress));
}
private void DoWork(string profile, IProgress<int> progress)
{
    for (int i = 0; i <= 100; i++)
    {
        Console.WriteLine(profile + i);
        Thread.Sleep(50); // Simulate work
        progress.Report(i); // Report progress
    }
}

advanced example [2]




InvokeRequired

C#:
//last decade used the synchronous InvokeRequired
//if (label1.InvokeRequired) { label1.Invoke(new Action(() => label1.Text = "Updated from async method")); } else { label1.Text = "Updated from async method"; }

// use BeginInvoke to update the UI from a #background thread#. It ensures that the update is #marshaled back# to the UI thread.
label1.BeginInvoke(new Action(() => label1.Text = "Updated from async method"));

the BeginInvoke ( checks internally if InvokeRequired ) and is asynchronous.


Method does not support async Task

In case the 'heavy lifting' method is in DLL and doesnt support async Task the solution is to wrap it as :
C#:
//sample1
internal static async Task<bool> CheckIfExists(string countryId)
{
    return Convert.ToBoolean(await Task.Run(() => General.db.ExecuteScalar("select CASE WHEN COUNT(id) > 0 THEN 1 ELSE 0 END from Customers where country=" + countryId)));
}

//sample2 - for the sake of example
internal static async Task<DataTable> ReadExcelSheetAsync(string filePath)
{
    return await Task.Run(() =>
    {
        //string connString = $@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={filePath};Extended Properties='Excel 12.0 Xml;HDR=YES;'";
        string connString = $@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={filePath};Extended Properties='Excel 8.0;HDR=YES;'";

        DataTable dataTable = new DataTable();

        using (OleDbConnection connection = new OleDbConnection(connString))
        {
            try
            {
                connection.Open();

                string query = "SELECT * FROM [Sheet1$]";
                using (OleDbCommand command = new OleDbCommand(query, connection))
                {
                    using (OleDbDataAdapter adapter = new OleDbDataAdapter(command))
                    {
                        adapter.Fill(dataTable);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
                throw new Exception(ex.Message);
            }
        }

        return dataTable;
    });
}

ConfigureAwait
await General.ReadExcelSheetAsync("hi"); //or else .ConfigureAwait(true);
after the awaited task completes, the continuation (the code that follows) will run on the same context (e.g., the UI thread in a WinForms or WPF application).

await General.ReadExcelSheetAsync("hi").ConfigureAwait(false);
the continuation does not need to run on the original context (e.g., the UI thread). Is often used in library code or background processing where you don't need to interact with the UI, as it can improve performance and avoid potential deadlocks.

In ASP.NET (like MVC or Web Forms) by default, it behaves like ConfigureAwait(true), the continuation will run on the original request context. Using .ConfigureAwait(false) is often recommended to avoid potential deadlocks and improve performance.

In ASP.NET Core by default, it behaves like ConfigureAwait(false) because there is no synchronization context to capture. Continuations run on thread pool threads.

Process simultaneously (aka concurrently) - Task.WhenAll
C#:
//source - https://forum.aspose.com/t/279770/2
public async Task Test001Async()
{
    await TestAsync();
}

private static async Task TestAsync()
{
    List<Task> tasks = new List<Task>();

    for (int i = 0; i < 5; i++)
    {
        string input = $@"C:\Temp\in{i}.docx";
        string output = $@"C:\Temp\out{i}.pdf";

        Task task = Task.Run(() =>
        {
            Document doc = new Document(input);
            doc.Save(output);
        });
        tasks.Add(task);
    }

    await Task.WhenAll(tasks);
}

////////////////////////////////////////same logic supporting result of each task
public async Task Test001Async()
{
    List<bool> results = await TestAsync();
}

private static async Task<List<bool>> TestAsync()
{
    List<Task<bool>> tasks = new List<Task<bool>>();

    for (int i = 0; i < 5; i++)
    {
        string input = $@"C:\Temp\in{i}.docx";
        string output = $@"C:\Temp\out{i}.pdf";

        Task<bool> task = Task.Run(() =>
        {
            try
            {
                Document doc = new Document(input);
                doc.Save(output);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        });
        tasks.Add(task);
    }

    return await Task.WhenAll(tasks);
}

Parallel.ForEach does not support await because it is designed for synchronous parallel execution.



Avoid duplication when multiple asynchronous operations are running concurrently

use SemaphoreSlim [2] class

C#:
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

//sample 1
private async void button_Click(object sender, EventArgs e)
{
    await semaphore.WaitAsync();
    try
    {
        // Your async operations here
    }
    finally
    {
        semaphore.Release();
    }
}

//sample 2
private async Task<long> InsertGetNewId()
{
    await semaphore.WaitAsync();
    try
    {
        //return await Task.Run(() =>
        // Or with async, start the async operation in a Task
        return await Task.Run(async () =>
        {
            // Your async operations here
            return newValue; // Return the new value
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Optionally rethrow the exception
    }
    finally
    {
        semaphore.Release();
    }
}

ref
https://grantwinney.com/using-async-await-and-task-to-keep-the-winforms-ui-more-responsive/

2013
https://learn.microsoft.com/en-us/s...ip-1-async-void-top-level-event-handlers-only
 
Top