Asked  7 Months ago    Answers:  5   Viewed   64 times

In my C#/XAML metro app, there's a button which kicks off a long-running process. So, as recommended, I'm using async/await to make sure the UI thread doesn't get blocked:

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{
     await GetResults();
}

private async Task GetResults()
{ 
     // Do lot of complex stuff that takes a long time
     // (e.g. contact some web services)
  ...
}

Occasionally, the stuff happening within GetResults would require additional user input before it can continue. For simplicity, let's say the user just has to click a "continue" button.

My question is: how can I suspend the execution of GetResults in such a way that it awaits an event such as the click of another button?

Here's an ugly way to achieve what I'm looking for: the event handler for the continue" button sets a flag...

private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
    _continue = true;
}

... and GetResults periodically polls it:

 buttonContinue.Visibility = Visibility.Visible;
 while (!_continue) await Task.Delay(100);  // poll _continue every 100ms
 buttonContinue.Visibility = Visibility.Collapsed;

The polling is clearly terrible (busy waiting / waste of cycles) and I'm looking for something event-based.

Any ideas?

Btw in this simplified example, one solution would be of course to split up GetResults() into two parts, invoke the first part from the start button and the second part from the continue button. In reality, the stuff happening in GetResults is more complex and different types of user input can be required at different points within the execution. So breaking up the logic into multiple methods would be non-trivial.

 Answers

35

You can use an instance of the SemaphoreSlim Class as a signal:

private SemaphoreSlim signal = new SemaphoreSlim(0, 1);

// set signal in event
signal.Release();

// wait for signal somewhere else
await signal.WaitAsync();

Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

// complete task in event
tcs.SetResult(true);

// wait for task somewhere else
await tcs.Task;
Tuesday, June 1, 2021
 
Saurabh
answered 7 Months ago
29

The best answer, in my opinion, is to cancel the Form from closing. Always. Cancel it, display your dialog however you want, and once the user is done with the dialog, programatically close the Form.

Here's what I do:

async void Window_Closing(object sender, CancelEventArgs args)
{
    var w = (Window)sender;
    var h = (ObjectViewModelHost)w.Content;
    var v = h.ViewModel;

    if (v != null &&
        v.IsDirty)
    {
        args.Cancel = true;
        w.IsEnabled = false;

        // caller returns and window stays open
        await Task.Yield();

        var c = await interaction.ConfirmAsync(
            "Close",
            "You have unsaved changes in this window. If you exit they will be discarded.",
            w);
        if (c)
            w.Close();

        // doesn't matter if it's closed
        w.IsEnabled = true;
    }
}

It is important to note the call to await Task.Yield(). It would not be necessary if the async method being called always executed asynchronously. However, if the method has any synchronous paths (ie. null-check and return, etc...) the Window_Closing event will never finish execution and the call to w.Close() will throw an exception.

Thursday, June 3, 2021
 
yosemite
answered 6 Months ago
76

In most project types, your async "up" and "down" will end at an async void event handler or returning a Task to your framework.

However, Console apps do not support this.

You can either just do a Wait on the returned task:

static void Main()
{
  MainAsync().Wait();
  // or, if you want to avoid exceptions being wrapped into AggregateException:
  //  MainAsync().GetAwaiter().GetResult();
}

static async Task MainAsync()
{
  ...
}

or you can use your own context like the one I wrote:

static void Main()
{
  AsyncContext.Run(() => MainAsync());
}

static async Task MainAsync()
{
  ...
}

More information for async Console apps is on my blog.

Saturday, June 5, 2021
 
ericstumper
answered 6 Months ago
27

No, the compiler has no special knowledge of IObservable<T>. As per section 7.7.7.1 of the C# 5 specification if the object has a method or there is an extension method in scope named GetAwaiter that returns a type that implements System.Runtime.CompilerServices.INotifyCompletion, it can be awaited. See Steven Toub's article, Await anything.

More specifically, from the spec

The task of an await expression is required to be awaitable. An expression t is awaitable if one of the following holds:
- t is of compile time type dynamic
- t has an accessible instance or extension method called GetAwaiter with no parameters and no type parameters, and a return type A for which all of the following hold:
1. A implements the interface System.Runtime.CompilerServices.INotifyCompletion (hereafter known as INotifyCompletion for brevity)
2. A has an accessible, readable instance property IsCompleted of type bool
3. A has an accessible instance method GetResult with no parameters and no type parameters

Note how this is similar to how foreach does not require IEnumerable<T> but simply a GetEnumerator method that returns a compatible object. This sort of duck typing is a performance optimization that allows value types to be used by the compiler without boxing. This can be used to avoid unnecessary allocations in performance sensitive code.

Monday, August 16, 2021
 
Connor Johnson
answered 4 Months ago
77

what if we have an IO operation that happens in a method that is not declared as async?

In this case, the I/O operation is blocking. In other words, GetResultFromWeb blocks the calling thread. Keep that in mind as we go through the rest...

I must use this method to do so.

By this I infer that you cannot write a GetResultFromWebAsync method which is asynchronous. So any thread doing the web requests must be blocked.

is there any way of performing these IO operations asynchronously without having to create one thread for each query?

The most natural approach is to write a GetResultFromWebAsync method. Since that isn't possible, your options are: block the calling thread, or block some other thread (i.e., a thread pool thread). Blocking a thread pool thread is a technique I call "fake asynchrony" - since it appears asynchronous (i.e., not blocking the UI thread) but it's really not (i.e., it just blocks a thread pool thread instead).

If I could, I'd like to create as less threads as possible, without having to block any of the threads.

That's not possible given the constraints. If you must use the GetResultFromWeb method, and that method blocks the calling thread, then a thread must be blocked.

One way I found to do so was by implementing APM, following this article from Jeffrey Richter and then, in my Begin method, I call ThreadPool.QueueWorkItem(GetResultFromWeb, asyncResult).

In this case, your code is exposing an asynchronous API (begin/end), but in the implementation it's just calling GetResultFromWeb on a thread pool thread. I.e., it is fake asynchrony.

This seems to work well.

It works, but it's not truly asynchronous.

But after reading so much that APM is obsolete, I was wondering how could I do this using TAP.

As others have noted, there's a much easier way to schedule work to the thread pool: Task.Run.

True asynchrony isn't possible, because you have a blocking method that you must use. So, all you can do is a workaround - fake asynchrony, a.k.a. blocking a thread pool thread. The easiest way to do that is:

Task<string> getResultTask = Task.Run(() => GetResultFromWeb(myUrl)); 
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

(much cleaner code than APM and AsyncEnumerator)

Note that I do not recommend creating a GetResultFromWebAsync method that is implemented using fake asynchrony. The Task-returning, Async-suffix methods are supposed to follow the Task-based Asynchronous Pattern guidelines, which imply true asynchrony.

In other words, as I describe in more detail on my blog, use Task.Run to invoke a method, not to implement a method.

Wednesday, August 18, 2021
 
amustill
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share