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


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