How to await an async callback

I’m trying to await a callback that is fired when a button is pressed.

maxresdefault

The important point is that I want to wait for the callback from a simple await without reshaping the code, that could be a loop of batches which require a user confirmation at each step.

We can also add a cancellation option if the user closes the dialog.

BatchProgressVM progressVM = new BatchProgressVM();
BatchProgress batchProgress = new BatchProgress();
progressVM.ProgressValue = 0;
progressVM.Log = msg => this.Status = msg;
progressVM.ProgressMessage = "Click Next to continue";
batchProgress.DataContext = progressVM;
batchProgress.Show();
CancellationTokenSource cts = new CancellationTokenSource();
batchProgress.Closed += OnClose;
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
void OnClose(object sender, EventArgs e)
{
    cts.Cancel();
}

In other words I want to achieve the following:

// e.g. it can be used in a loop
internal async Task BatchLogic(int num, int tot, CancellationTokenSource cts, Dispatcher dispatcher)
{
    taskNum = num;
    totTask = tot;
    dispatcher.Invoke(() =>
       {
           ProgressMessage = $"task {taskNum-1} of {totTask} done! Click Next!";
           IsBusy = false; // enable Next button for user to confirm going on
       });
    cts.Token.Register(() =>
    {
         if (callback != null) callback.Invoke(Task.FromResult(false));
    });
    // e.g. await that the user confirms
    await Task.Factory.FromAsync(beginMethod, endMethod, null);
    ProgressValue = 100 * taskNum / totTask;
}

with the following defintions

// define a closure for the callback
AsyncCallback callback; 
//pass the callback to the closure from the beginMethod
private IAsyncResult beginMethod(AsyncCallback callback, object state)
{
    this.callback = callback;
    return Task.FromResult(true);
}

private void endMethod(IAsyncResult obj)
{
    Task taskRes = obj as Task;
    // check if it is completed or cancelled
    bool res = taskRes?.Result ?? true;
    if (res)
    {
        ProgressMessage = $"task {taskNum} of {totTask} working! Pls wait";
        IsBusy = true; // disable Next button while working
    }
}

//and finally invoke it from the event (i.e. the Command method in the MVVM for WPF)
private async void RunNext()
{
    if (callback != null)
    {
        callback.Invoke(Task.FromResult(true));
    }
}

As I said, the usage is very convenient inside a loop

await progressVM.BatchLogic(batch_num, tot_batch, cts, dispatcher);
if (cts.IsCancellationRequested) break;

In the end (e.g. after the async loop) you can show the results and unsuscribe the Close event.

Status = (cts.IsCancellationRequested ? "Cancelled!" : "Done!");
batchProgress.Closed -= OnClose;
batchProgress.Close();

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s