Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 639 Vote(s) - 3.49 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Is it possible to await an event instead of another async method?

#1
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.






Reply

#2
Ideally, you *don't*. While you certainly can block the async thread, that's a waste of resources, and not ideal.

Consider the canonical example where the user goes to lunch while the button is waiting to be clicked.

If you have halted your asynchronous code while waiting for the input from the user, then it's just wasting resources while that thread is paused.

That said, it's better if in your asynchronous operation, you set the state that you need to maintain to the point where the button is enabled and you're "waiting" on a click. At that point, your `GetResults` method **stops**.

Then, when the button *is* clicked, based on the state that you have stored, you start *another asynchronous task* to continue the work.

Because the [`SynchronizationContext`][1] will be captured in the event handler that calls `GetResults` (the compiler will do this as a result of using the `await` keyword being used, and the fact that [SynchronizationContext.Current][2] should be non-null, given you are in a UI application), you can use [`async`/`await`][3] like so:

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

// Show dialog/UI element. This code has been marshaled
// back to the UI thread because the SynchronizationContext
// was captured behind the scenes when
// await was called on the previous line.
...

// Check continue, if true, then continue with another async task.
if (_continue) await ContinueToGetResultsAsync();
}

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

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

`ContinueToGetResultsAsync` is the method that continues to get the results in the event that your button is pushed. If your button is *not* pushed, then your event handler does nothing.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply

#3
You can use an instance of the [SemaphoreSlim Class][1] 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][2] to create a [Task<T>][3] 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;


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply

#4
When you have an unusual thing you need to `await` on, the easiest answer is often `TaskCompletionSource` (or some `async`-enabled primitive based on `TaskCompletionSource`).

In this case, your need is quite simple, so you can just use `TaskCompletionSource` directly:

private TaskCompletionSource<object> continueClicked;

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
// Note: You probably want to disable this button while "in progress" so the
// user can't click it twice.
await GetResults();
// And re-enable the button here, possibly in a finally block.
}

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

// Wait for the user to click Continue.
continueClicked = new TaskCompletionSource<object>();
buttonContinue.Visibility = Visibility.Visible;
await continueClicked.Task;
buttonContinue.Visibility = Visibility.Collapsed;

// More work...
}

private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
if (continueClicked != null)
continueClicked.TrySetResult(null);
}

Logically, `TaskCompletionSource` is like an `async` `ManualResetEvent`, except that you can only "set" the event once and the event can have a "result" (in this case, we're not using it, so we just set the result to `null`).
Reply

#5
Here is a utility class that I use:

public class AsyncEventListener
{
private readonly Func<bool> _predicate;

public AsyncEventListener() : this(() => true)
{

}

public AsyncEventListener(Func<bool> predicate)
{
_predicate = predicate;
Successfully = new Task(() => { });
}

public void Listen(object sender, EventArgs eventArgs)
{
if (!Successfully.IsCompleted && _predicate.Invoke())
{
Successfully.RunSynchronously();
}
}

public Task Successfully { get; }
}

And here is how I use it:

var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;

// ... make it change ...

await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Reply

#6
**Simple Helper Class:**


public class EventAwaiter<TEventArgs>
{
private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();

private readonly Action<EventHandler<TEventArgs>> _unsubscribe;

public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
{
subscribe(Subscription);
_unsubscribe = unsubscribe;
}

public Task<TEventArgs> Task => _eventArrived.Task;

private EventHandler<TEventArgs> Subscription => (s, e) =>
{
_eventArrived.TrySetResult(e);
_unsubscribe(Subscription);
};
}

**Usage:**

var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through