1

We have a WinForm application that has a base form that all other forms inherit from.
On the base form there is a Delete button and the Delete button calls a virtual bool method called DeleteData.

public virtual void DeleteButton_Click(object sender, EventArgs e)
{
    if (DeleteData())
    {
        // run some cleanup code
        DoSomeCleanup();
    }
}

public virtual bool DeleteData()
{
    throw new NotImplementedException("Not implemented.");
}

Child form has overridden the DeleteData method

public override bool DeleteData()
{
    try
    {
        // Delete some data

        // Call async method that does some UI stuff and wait on it to finish
        SomeSharedAsyncMethod();

        return true;
    }
    catch(Exception ex)
    {
        // Handle exception

        return false;
    }
}

Here is the catch, SomeSharedAsyncMethod is mark as async and inside of it, it does some UI stuff like binding to textboxes and this method is called by others so the method must stay marked as "async"

public async Task SomeSharedAsyncMethod()
{
    // Some code that has await and also some code that updates textboxes or other UI controls.

    await Task.Delay(2000);
    SomeTextBox.Text = "Some Text";
}

I can't add "async" to the "DeleteData" method because the base form is looking at the DeleteData method to run "DoSomeCleanup" and "DoSomeCleanup" would get called before DeleteData is finished.
Let's also assume that I can't add "async" to the delete button because I don't have control of that project.
I also don't want to override the DeleteButton_Click because I don't want to copy all the code that is located inside the base form DeleteButton_Click.

Here are some things I have tried:

public override bool DeleteData()
{
    // Delete some data

    // Call async method that does some UI stuff and wait on it to finish

    // Causes a deadlock
    SomeSharedAsyncMethod().Wait();

    // RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
    SomeSharedAsyncMethod().RunSynchronously();

    // This will not wait on SomeSharedAsyncMethod to execute
    SomeSharedAsyncMethod().ConfigureAwait(false);

    // Cross-thread operation not valid
    Task.Run(async () => { await SomeSharedAsyncMethod(); }).Wait();

    // This will not wait on SomeSharedAsyncMethod to execute
    Task.Run(() => { SomeSharedAsyncMethod(); }).Wait();

    // This will not wait on SomeSharedAsyncMethod to execute
    Task.Run(async () => { await SomeSharedAsyncMethod().ConfigureAwait(false); }).Wait();

    // This will not wait on SomeSharedAsyncMethod to execute
    Task.Factory.StartNew(() =>
    {
        SomeSharedAsyncMethod().ConfigureAwait(false);
    }, Task.Factory.CancellationToken, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()).Wait();

    return true;
}

We are looking for a way of getting the DeleteData method to run all its code and not return until all lines of code has completed, this includes the SomeSharedAsyncMethod.

goroth
  • 2,510
  • 5
  • 35
  • 66

2 Answers2

0

I don't thinks there is a way to wait for SomeSharedAsyncMethod inside the DeleteData.

The DeleteData method occupies the UI thread. It needes SomeSharedAsyncMethod to be completed to release the UI thread, but SomeSharedAsyncMethod starts after DeleteData and it needs the UI thread too. In other words, you need to run something on the UI thread to be able to release it. This is the classic deadlock example. And this is exacly why you shouldn't mix the async and .Wait() in the first place.

So, how this can be fixed? I would suggest to remove all UI-related code from SomeSharedMethodAsync and add .ConfigureAwait(false) to all async method invocations inside it.

public override bool DeleteData()
{
    SomeSharedMethodAsync().ConfigureAwait(false).Wait();
    UpdateUI();
}

async Task SomeSharedAsyncMethod()
{
    // do all async stuff here, but DON'T update the UI
    await Task.Delay(2000).ConfigureAwait(false);
}

void UpdateUI()
{
     // update the UI here
     SomeTextBox.Text = "Some Text";
}

Of course, this should be done only as a last resort. Making all methods starting with DeleteButton_Click asynchronous is much much preferred.

Maxim Kosov
  • 1,930
  • 10
  • 19
  • The article sent by "oopsdazie -- above" worked and did not require modification of the other methods, but I am trying to get the Nito.AsyncEx to do the same. https://stackoverflow.com/a/5097066/5779825 – goroth Oct 10 '17 at 23:27
0

There were two options I tested and both worked.

  1. How would I run an async Task<T> method synchronously?

This was sent my oopsdazie in comment above.

check this out: stackoverflow.com/a/5097066/5779825. Hope it helps – oopsdazie Oct 10 at 20:35

Which was a repost from here https://social.msdn.microsoft.com/Forums/en-US/163ef755-ff7b-4ea5-b226-bbe8ef5f4796/is-there-a-pattern-for-calling-an-async-method-synchronously?forum=async

Basically it is an async helper class that works against its own SynchronizationContext.

  1. Use Stehpen Cleary -- Nito AsyncEx NuGet

https://github.com/StephenCleary/AsyncEx

Nito.AsyncEx.AsyncContext.Run(async () => await SomeSharedAsyncMethod());  

I went with option 2 since it has a lot of other handy async helpers

goroth
  • 2,510
  • 5
  • 35
  • 66