Cefsharp Shutdown incorrect - c#

Exception: Cef.Shutdown must be called on the same thread that Cef.Initialize was called - typically your UI thread. If you called Cef.Initialize on a Thread other than the UI thread then you will need to call Cef.Shutdown on the same thread. Cef.Initialize was called on ManagedThreadId: 1where Cef.Shutdown is being called on ManagedThreadId: 4
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
}
private static async Task MainAsync()
{
List<string> urls = new List<string>();
urls.Add("https://google.com");
CefSharpWrapper wrapper = new CefSharpWrapper();
wrapper.InitializeBrowser();
foreach (string url in urls)
{
await wrapper.GetResultAfterPageLoad(url);
}
wrapper.ShutdownBrowser();
}
}
public sealed class CefSharpWrapper
{
private ChromiumWebBrowser _browser;
public void InitializeBrowser()
{
Cef.Initialize(new CefSettings());
_browser = new ChromiumWebBrowser();
AutoResetEvent waitHandleOnBrowserInitialized = new AutoResetEvent(false);
EventHandler onBrowserInitialized = null;
onBrowserInitialized = async (sender, e) =>
{
_browser.BrowserInitialized -= onBrowserInitialized;
waitHandleOnBrowserInitialized.Set();
};
_browser.BrowserInitialized += onBrowserInitialized;
waitHandleOnBrowserInitialized.WaitOne();
}
public Task<bool> GetResultAfterPageLoad(string pageUrl)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> onPageLoaded = null;
// An event that is fired when the first page is finished loading.
// This returns to us from another thread.
onPageLoaded = async (sender, args) =>
{
// Check to see if loading is complete - this event is called twice, one when loading starts
// second time when it's finished
// (rather than an iframe within the main frame).
if (!args.IsLoading)
{
// Remove the load event handler, because we only want one snapshot of the initial page.
_browser.LoadingStateChanged -= onPageLoaded;
tcs.SetResult(true);
}
};
_browser.LoadingStateChanged += onPageLoaded;
_browser.Load(pageUrl);
return tcs.Task;
}
public void ShutdownBrowser()
{
// Clean up Chromium objects. You need to call this in your application otherwise
// you will get a crash when closing.
Cef.Shutdown();
}
}

Amaitland, thanks for the reply.
class Program
{
static void Main(string[] args)
{
CefSharpWrapper.InitializeBrowser();
MainAsync().Wait();
CefSharpWrapper.ShutdownBrowser();
}
private static async Task MainAsync()
{
List<string> urls = new List<string>();
urls.Add("https://google.com");
CefSharpWrapper wrapper = new CefSharpWrapper();
foreach (string url in urls)
{
await wrapper.GetResultAfterPageLoad(url);
}
}
}
public sealed class CefSharpWrapper
{
private static ChromiumWebBrowser _browser;
public static void InitializeBrowser()
{
Cef.Initialize(new CefSettings());
_browser = new ChromiumWebBrowser();
AutoResetEvent waitHandleOnBrowserInitialized = new AutoResetEvent(false);
EventHandler onBrowserInitialized = null;
onBrowserInitialized = async (sender, e) =>
{
_browser.BrowserInitialized -= onBrowserInitialized;
waitHandleOnBrowserInitialized.Set();
};
_browser.BrowserInitialized += onBrowserInitialized;
waitHandleOnBrowserInitialized.WaitOne();
}
public Task<bool> GetResultAfterPageLoad(string pageUrl)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> onPageLoaded = null;
// An event that is fired when the first page is finished loading.
// This returns to us from another thread.
onPageLoaded = async (sender, args) =>
{
// Check to see if loading is complete - this event is called twice, one when loading starts
// second time when it's finished
// (rather than an iframe within the main frame).
if (!args.IsLoading)
{
// Remove the load event handler, because we only want one snapshot of the initial page.
_browser.LoadingStateChanged -= onPageLoaded;
tcs.SetResult(true);
}
};
_browser.LoadingStateChanged += onPageLoaded;
_browser.Load(pageUrl);
return tcs.Task;
}
public static void ShutdownBrowser()
{
// Clean up Chromium objects. You need to call this in your application otherwise
// you will get a crash when closing.
Cef.Shutdown();
}
}

Related

How do I convert event callbacks to async tasks in C#? [duplicate]

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.
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;
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).
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;
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 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 should be non-null, given you are in a UI application), you can use async/await 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.
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;
Stephen Toub published this AsyncManualResetEvent class on his blog.
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
With Reactive Extensions (Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
You can add Rx with Nuget Package System.Reactive
Tested Sample:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
I'm using my own AsyncEvent class for awaitable events.
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;
public class AsyncEvent : AsyncEvent<EventArgs>
{
public AsyncEvent() : base()
{
}
}
public class AsyncEvent<T> where T : EventArgs
{
private readonly HashSet<AsyncEventHandler<T>> _handlers;
public AsyncEvent()
{
_handlers = new HashSet<AsyncEventHandler<T>>();
}
public void Add(AsyncEventHandler<T> handler)
{
_handlers.Add(handler);
}
public void Remove(AsyncEventHandler<T> handler)
{
_handlers.Remove(handler);
}
public async Task InvokeAsync(object sender, T args)
{
foreach (var handler in _handlers)
{
await handler(sender, args);
}
}
public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
var result = left ?? new AsyncEvent<T>();
result.Add(right);
return result;
}
public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
left.Remove(right);
return left;
}
}
To declare an event in the class that raises events:
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
To raise the events:
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
To subscribe to the events:
MyControl.Click += async (sender, args) => {
// await...
}
MyControl.Click += (sender, args) => {
// synchronous code
return Task.CompletedTask;
}
Here is a small toolbox of six methods, that can be used for converting events to tasks:
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler)
{
var tcs = new TaskCompletionSource<object>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, EventArgs e)
{
removeHandler(Handler);
tcs.SetResult(null);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, TEventArgs e)
{
removeHandler(Handler);
tcs.SetResult(e);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate handler = default;
Action<object, TEventArgs> genericHandler = (sender, e) =>
{
removeHandler(handler);
tcs.SetResult(e);
};
handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
.CreateDelegate(typeof(TDelegate), genericHandler);
addHandler(handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<object>();
EventHandler handler = default;
handler = new EventHandler((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(null);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = default;
handler = new EventHandler<TEventArgs>((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(e);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
Action<Action<TArgument>> addHandler,
Action<Action<TArgument>> removeHandler)
{
var tcs = new TaskCompletionSource<TArgument>();
addHandler(Handler);
return tcs.Task;
void Handler(TArgument arg)
{
removeHandler(Handler);
tcs.SetResult(arg);
}
}
All these methods are creating a Task that will complete with the next invocation of the associated event. This task can never become faulted or canceled, it may only complete successfully.
Usage example with a standard event (Progress<T>.ProgressChanged):
var p = new Progress<int>();
//...
int result = await EventToAsync<int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<EventHandler<int>, int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<int>(p, "ProgressChanged");
Usage example with a non-standard event:
public static event Action<int> MyEvent;
//...
int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);
The event is unsubscribed when the task is completed. No mechanism is provided for unsubscribing earlier than that.
Here is a class I used for testing, which support CancellationToken.
This Test method shows us awaiting an instance of ClassWithEvent's MyEvent to be raised. :
public async Task TestEventAwaiter()
{
var cls = new ClassWithEvent();
Task<bool> isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(3));
cls.Raise();
Assert.IsTrue(await isRaisedTask);
isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(1));
System.Threading.Thread.Sleep(2000);
Assert.IsFalse(await isRaisedTask);
}
Here's the event awaiter class.
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
It basically relies on CancellationTokenSource to report back the result.
It uses some IL injection to create a delegate to match the event's signature.
That delegate is then added as a handler for that event using some reflection.
The body of the generate method simply calls another function on the EventAwaiter class, which then reports success using the CancellationTokenSource.
Caution, do not use this, as is, in product. This is meant as a working example.
For instance, IL generation is an expensive process. You should avoid regenerate the same method over and over again, and instead cache these.
AsyncEx has AsyncManualResetEvent for this. You can:
var signal = new AsyncManualResetEvent();
await signal.WaitAsync();
And trigger it with:
signal.Set();

WebSocketSharp await/read for server response in client [duplicate]

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.
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;
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).
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;
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 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 should be non-null, given you are in a UI application), you can use async/await 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.
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;
Stephen Toub published this AsyncManualResetEvent class on his blog.
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
With Reactive Extensions (Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
You can add Rx with Nuget Package System.Reactive
Tested Sample:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
I'm using my own AsyncEvent class for awaitable events.
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;
public class AsyncEvent : AsyncEvent<EventArgs>
{
public AsyncEvent() : base()
{
}
}
public class AsyncEvent<T> where T : EventArgs
{
private readonly HashSet<AsyncEventHandler<T>> _handlers;
public AsyncEvent()
{
_handlers = new HashSet<AsyncEventHandler<T>>();
}
public void Add(AsyncEventHandler<T> handler)
{
_handlers.Add(handler);
}
public void Remove(AsyncEventHandler<T> handler)
{
_handlers.Remove(handler);
}
public async Task InvokeAsync(object sender, T args)
{
foreach (var handler in _handlers)
{
await handler(sender, args);
}
}
public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
var result = left ?? new AsyncEvent<T>();
result.Add(right);
return result;
}
public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
left.Remove(right);
return left;
}
}
To declare an event in the class that raises events:
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
To raise the events:
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
To subscribe to the events:
MyControl.Click += async (sender, args) => {
// await...
}
MyControl.Click += (sender, args) => {
// synchronous code
return Task.CompletedTask;
}
Here is a small toolbox of six methods, that can be used for converting events to tasks:
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler)
{
var tcs = new TaskCompletionSource<object>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, EventArgs e)
{
removeHandler(Handler);
tcs.SetResult(null);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, TEventArgs e)
{
removeHandler(Handler);
tcs.SetResult(e);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate handler = default;
Action<object, TEventArgs> genericHandler = (sender, e) =>
{
removeHandler(handler);
tcs.SetResult(e);
};
handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
.CreateDelegate(typeof(TDelegate), genericHandler);
addHandler(handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<object>();
EventHandler handler = default;
handler = new EventHandler((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(null);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = default;
handler = new EventHandler<TEventArgs>((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(e);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
Action<Action<TArgument>> addHandler,
Action<Action<TArgument>> removeHandler)
{
var tcs = new TaskCompletionSource<TArgument>();
addHandler(Handler);
return tcs.Task;
void Handler(TArgument arg)
{
removeHandler(Handler);
tcs.SetResult(arg);
}
}
All these methods are creating a Task that will complete with the next invocation of the associated event. This task can never become faulted or canceled, it may only complete successfully.
Usage example with a standard event (Progress<T>.ProgressChanged):
var p = new Progress<int>();
//...
int result = await EventToAsync<int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<EventHandler<int>, int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<int>(p, "ProgressChanged");
Usage example with a non-standard event:
public static event Action<int> MyEvent;
//...
int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);
The event is unsubscribed when the task is completed. No mechanism is provided for unsubscribing earlier than that.
Here is a class I used for testing, which support CancellationToken.
This Test method shows us awaiting an instance of ClassWithEvent's MyEvent to be raised. :
public async Task TestEventAwaiter()
{
var cls = new ClassWithEvent();
Task<bool> isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(3));
cls.Raise();
Assert.IsTrue(await isRaisedTask);
isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(1));
System.Threading.Thread.Sleep(2000);
Assert.IsFalse(await isRaisedTask);
}
Here's the event awaiter class.
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
It basically relies on CancellationTokenSource to report back the result.
It uses some IL injection to create a delegate to match the event's signature.
That delegate is then added as a handler for that event using some reflection.
The body of the generate method simply calls another function on the EventAwaiter class, which then reports success using the CancellationTokenSource.
Caution, do not use this, as is, in product. This is meant as a working example.
For instance, IL generation is an expensive process. You should avoid regenerate the same method over and over again, and instead cache these.
AsyncEx has AsyncManualResetEvent for this. You can:
var signal = new AsyncManualResetEvent();
await signal.WaitAsync();
And trigger it with:
signal.Set();

C# WPF program button click run a task until another button click stop or until cancel token valid

I am creating a WPF app where I want to have a global bool im assuming, on the first button click I’ll set this bool to true and I want it to run a task (continuously call an API method) until I click the button again and it stops it. What would be the best way to do this?
private bool running = false;
private async void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
if (!running)
{
running = true;
}
else
running = false;
if (running)
{
RunningNrunnin(running);
//tradeClient.GetTradeHistory();
}
}
public void RunningNrunnin(bool running)
{
if (running)
{
Task task = new Task(() =>
{
while (running)
{
GetTradeHistory();
Thread.Sleep(2000);
}
});
task.Start();
}
}
Added Below
I would like to call a method over and over until the user creates a cancel request on a thread in the background. I currently had it so I can call a action (a counter) and update the GUI each second but when I try to do this same thing with a method call it executes only once.
// Here is the method I want to call continously until canceled
private async void HistoryTest()
{
cancellationToken = new CancellationTokenSource();
task = Task.Factory.StartNew(async () =>
{
while (true)
{
cancellationToken.Token.ThrowIfCancellationRequested();
await Client2.GetHistory();
await Task.Delay(2000);
}
}, cancellationToken.Token);
}
public async Task GetHistory()
{
try
{
var response = await Client.Service.GetDataAsync
(
ProductType.BtcUsd,
5,
1
);
}
catch(Exception)
{
throw;
}
}
I made a little console test app to test this so I had to change the method signatures (static) and can't use ButtonClick on a console. I simulated the button click by putting as sleep between the programatic "button click".
This might get you started.
private static bool isRunning = false;
private static int clickCounter = 0;
private static int iterationsCounter = 0;
static void Main(string[] args)
{
Console.WriteLine(“Start”);
for(int i = 0; i < 7; i++)
{
BtnTrade1_Buy_Click();
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine(“END”);
}
private static async Task BtnTrade1_Buy_Click()
{
iterationsCounter = 0;
isRunning = !isRunning;
Console.WriteLine($"Ha: {isRunning} {clickCounter++}");
await RunningNrunnin();
}
private static async Task RunningNrunnin()
{
await Task.Run(() => Runit());
}
private static void Runit()
{
while (isRunning)
{
GetTradeHistory();
System.Threading.Thread.Sleep(100);
}
}
private static void GetTradeHistory()
{
Console.WriteLine($"Hello Test {iterationsCounter++}");
}
Of course you wouldn't need all the counters and the Console.WriteLine() stuff. They are there to allow you to visualize what is happening.
Let me know if you need more info.
You don't need to do anything else inside the BtnTrade1_Buy_Click event handler, beyond toggling the isRunning field:
private bool _isRunning;
private void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
_isRunning = !_isRunning;
}
The Task that is getting the trade history in a loop, needs to be started only once. You could start it in the Window_Loaded event. Storing the Task in a private field is a good idea, in case you decide to await it at some point, but if you are handling the exceptions inside the task it's not necessary.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_ = StartTradeHistoryLoopAsync(); // Fire and forget
}
private async Task StartTradeHistoryLoopAsync()
{
while (true)
{
var delayTask = Task.Delay(2000);
if (_isRunning)
{
try
{
await Task.Run(() => GetTradeHistory()); // Run in the ThreadPool
//GetTradeHistory(); // Alternative: Run in the UI thread
}
catch (Exception ex)
{
// Handle the exception
}
}
await delayTask;
}
}
Don't forget to stop the task when the window is closed.
private void Window_Closed(object sender, EventArgs e)
{
_isRunning = false;
}
This will stop the calls to GetTradeHistory(), but will not stop the loop. You may need to add one more private bool field to control the loop itself:
while (_alive) // Instead of while (true)

ObserveOnDispatcher dose not invoke the handler in UI thread

I have a message bus class which use Rx to push multiple threads events in WPF application.
My problem ObserveOnDispatcher does not invoke the event handler in the UI thread.
Code:
private void button_Click(object sender, RoutedEventArgs e)
{
var messageBus = new MessageBus();
messageBus.GetMessages<Message>().ObserveOnDispatcher().Subscribe(x => TestHanlder(x));
Trace.WriteLine("Main Thread Id:" + Thread.CurrentThread.ManagedThreadId);
var deviceManager = new DeviceManager(messageBus);
deviceManager.Start();
}
private void TestHanlder(Message message)
{
Trace.WriteLine("UI Handler ThreadId:" + Thread.CurrentThread.ManagedThreadId);
}
public class DeviceManager
{
private readonly MessageBus _messageBus;
public DeviceManager(MessageBus messageBus)
{
_messageBus = messageBus;
}
public void Start()
{
for (;;)
{
var t = Task.Factory.StartNew(() => BackGroundTask(), TaskCreationOptions.LongRunning);
t.Wait();
}
}
private void BackGroundTask()
{
Thread.Sleep(1000);
Trace.WriteLine("Push ThreadId:" + Thread.CurrentThread.ManagedThreadId);
var message = new Message();
_messageBus.Publish(message);
}
}
public class MessageBus
{
readonly ISubject<object> _messages;
public MessageBus()
{
_messages = new Subject<object>();
}
public void Publish<TMessage>(TMessage message)
{
_messages.OnNext(message);
}
public IObservable<TMessage> GetMessages<TMessage>()
{
return _messages.OfType<TMessage>();
}
}
public class Message
{
public Message()
{
}
}
Without ObserveOnDispatcher:
messageBus.GetMessages<Message>().Subscribe(x => TestHanlder(x));
..........................Output.................................
Main Thread Id:8
Push ThreadId:9
UI Handler ThreadId:9
But I need to execute the TestHanlder function in the main thread or UI thread, in my use case above it must be the thread number 8.
When I use ObserveOnDispatcher:
messageBus.GetMessages<Message>().ObserveOnDispatcher().Subscribe(x => TestHanlder(x));
..........................Output.................................
Main Thread Id:9
Push ThreadId:10
------------------> UI Handler ThreadId: are missing not there!?
What I'm doing wrong here?!!!!!
for (;;) { t.Wait(); } this code is executed in the UI thread and prevents it from executing anything else dispatched on it. ObserveOnDispatcher is working fine, but your dispatcher thread is blocked.
If you introduce async/await (that will release the thread), the scenario will work fine:
private async void button_Click(object sender, RoutedEventArgs e)
{
var messageBus = new MessageBus();
messageBus.GetMessages<Message>().ObserveOnDispatcher().Subscribe(x => TestHanlder(x));
Trace.WriteLine("Main Thread Id:" + Thread.CurrentThread.ManagedThreadId);
var deviceManager = new DeviceManager(messageBus);
await deviceManager.Start();
}
...
public async Task<Unit> Start()
{
for (;;)
{
await Task.Factory.StartNew(() => BackGroundTask(), TaskCreationOptions.LongRunning);
}
}

Asynchronous methods and a event

I use awesomium to automate a site. I tried to use asynchronous programming, because I didn't want my GUI to freeze, but I have a problem at a event (a popup appear and I want to make some actions in this popup until I close it) when the application doesn't continue how I want. After the event is fired I want my application to continue with the event method (webc_ShowCreatedWebView and after with popupTwitter(method), but I find that when executing JavaScript code, the control returns in the While from the first method. How could I do that after the earnpoints method is called and the events is fired to finish the event and the method and after that the control return in the while.
private async void button4_Click(object sender, EventArgs e)
{
Twitter twitter = new Twitter(webView);
twitter.Login(webView);
webView.ShowCreatedWebView += webc_ShowCreatedWebView;
addmefast.Login(webView);
int i = 0;
while (i < 10)
{
Task earnpoints = EarnPoints(webView);
await earnpoints;
//Here i don't want to continue until EarnPoints method > webc_ShowCreatedWebView event > popupTwitter method it's finished.
i++;
}
}
public async Task EarnPoints(IWebView web)
{
web.Source = "http://addmefast.com/free_points/twitter".ToUri();
await Task.Delay(3000);
web.ExecuteJavascript("document.getElementsByClassName('single_like_button btn3-wrap')[0].click();"); //event fired: webc_ShowCreatedWebView
}
async void webc_ShowCreatedWebView(object sender, ShowCreatedWebViewEventArgs e)
{
WebView view = new WebView(e.NewViewInstance);
await popupTwitter(view);
}
async Task popupTwitter(WebView view)
{
Popupform FormTwitter = new Popupform(view);
FormTwitter.Show();
await Task.Delay(6000);
FormTwitter.Twitter();
await Task.Delay(2000);
FormTwitter.Close();
await Task.Delay(4000);
}
I also had problems implementing async methods with awesomium, but got it working.
First I made this wrapper. Have to be created on the main thread.
public class AsyncWebView
{
public static SynchronizationContext _synchronizationContext;
private readonly WebView _webView;
public AsyncWebView()
{
_synchronizationContext = SynchronizationContext.Current;
_webView = WebCore.CreateWebView(1024, 900);
}
public async Task Navigate(String url)
{
Debug.WriteLine("Navigating");
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
FrameEventHandler handler = (sender, args) =>
{
Debug.WriteLine(args.Url);
if (!_webView.IsNavigating && !_webView.IsLoading)
tcs.SetResult(true);
};
_webView.LoadingFrameComplete += handler;
_synchronizationContext.Send(SetWebViewSource, url);
await tcs.Task;
_webView.LoadingFrameComplete -= handler;
Debug.WriteLine("Done");
}
private void SetWebViewSource(object url)
{
_webView.Source = new Uri((string)url);
}
}
Usage:
async Task test()
{
await webView.Navigate("http://www.nytimes.com");
Debug.WriteLine("All done");
}
Just make sure you have a SynchronizationContext where the AsyncWebView constructor is called from.

Categories

Resources