2

Is there a way to wait synchronously for an async method that runs on the same thread?

The desired effect is

  • to have the Worker() run asynchronously on the UI thread
  • and at the same time wait for it to finish before the Close() method returns

The example below enters in a deadlock, and if I make Form1_FormClosing() async I don't satisfy the second condition.

public partial class Form1 : Form
{
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    CancellationTokenSource cts = new CancellationTokenSource();
    public Form1()
    {
        InitializeComponent();
        Show();
        Worker(cts.Token); // async worker started on UI thread
    }

    async void Worker(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
            await TaskEx.Delay(1000);
        tcs.SetResult(true); // signal completition
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Close();
        MessageBox.Show("This is supposed to be second");
    }

    private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        cts.Cancel(); // request cancel 
        tcs.Task.Wait(); // deadlock
        await tcs.Task; // button1_Click() gets control back instead of Worker()
        MessageBox.Show("This is supposed to be first");
    }
}
Chris
  • 1,213
  • 1
  • 21
  • 38
  • 1
    Use `await Task.Delay(1000, ct).ConfigureAwait(false)`. and `tcs.TrySetResult(true);`. Make sure that any long operation performed in `Worker` is cancelable through the token. Also catch `OperationCanceledException` in the worker, and `tcs.SetCanceled()` in that case. – Alex Apr 05 '15 at 02:17
  • What is it you're actually trying to do? You've said what isn't working about your solution attempt, but you haven't said what the problem is you're attempting to solve. – Timothy Shields Apr 05 '15 at 02:34
  • Do you want to have the Task continue running, or cancel it at whatever interruption point it is paused? – Ben Voigt Apr 05 '15 at 03:19
  • @BenVoigt I want to cancel it at whatever point is paused. – Chris Apr 05 '15 at 10:44
  • @Chris, check this: [Cancelling a pending task synchronously on the UI thread](http://stackoverflow.com/questions/20876645/cancelling-a-pending-task-synchronously-on-the-ui-thread) – noseratio Apr 06 '15 at 06:15

1 Answers1

3

Is there a way to wait synchronously for an async method that runs on the same thread?

You don't need to synchronously wait. By making Worker async Task instead of async void you can get the desired behavior and remove the useless TaskCompletionSource:

private Task workerTask;
public Form()
{
     workerTask = Worker(cts.Token);
}

private async Task Worker(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
        await TaskEx.Delay(1000);
}

private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    cts.Cancel(); // request cancel
    await workerTask; // Wait for worker to finish before closing
}

I'm missing the implementation of Close(), but i suspect you could do without it and relay on the form closing event to cancel the worker.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Close() is called by another form and when it returns Worker() must be stopped, otherwise things that are modified by the other form will crash Worker(). Alex's suggestion is what I was looking for. When I request cancel the task is instantly killed, throwing an exception, and I don't have to worry that Worker() will access things I'm about to modify. – Chris Apr 05 '15 at 03:06
  • 1
    It would only be instantly killed if you use `ct.Token.ThrowIfCancellationRequested`. Are you using it somewhere? – Yuval Itzchakov Apr 05 '15 at 03:09
  • I do not, but it makes a similar behavior to Task.ConfigureAwait(false) that Alex suggested. – Chris Apr 05 '15 at 10:48