async method with completed event - c#

I use .net 4.0 and i've tried to figure out how to use async method to await DocumentCompleted event to complete and return the value. My original code is above, how can i turn it into async/await model in this scenario ?
private class BrowserWindow
{
private bool webBrowserReady = false;
public string content = "";
public void Navigate(string url)
{
xxx browser = new xxx();
browser.DocumentCompleted += new EventHandler(wb_DocumentCompleted);
webBrowserReady = false;
browser.CreateControl();
if (browser.IsHandleCreated)
browser.Navigate(url);
while (!webBrowserReady)
{
//Application.DoEvents(); >> replace it with async/await
}
}
private void wb_DocumentCompleted(object sender, EventArgs e)
{
try
{
...
webBrowserReady = true;
content = browser.Document.Body.InnerHtml;
}
catch
{
}
}
public delegate string AsyncMethodCaller(string url);
}

So we need a method that returns a task when the DocumentCompleted event fires. Anytime you need that for a given event you can create a method like this:
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
return tcs.Task;
}
Once you have that you can use:
await browser.WhenDocumentCompleted();

#Servy had the genius answer I was looking for, but it didn't apply to my use case well. I found errors when the event is raised multiple times due to the event handler trying to set the result on the TaskCompletionSource on subsequent event invocations.
I enhanced his answer in two ways. The first is simply to unsubscribe the DocumentCompleted event once it has been handled the first time.
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += DocumentCompletedHandler;
return tcs.Task;
void DocumentCompletedHandler(object sender, EventArgs e)
{
browser.DocumentCompleted -= DocumentCompletedHandler;
tcs.SetResult(true);
}
}
Note I'm using a local function here to capture the TaskCompletionSource instance, which requires a minimum of C# 7.0.
The second enhancement is to add a timeout. My particular use case was in unit tests, and I wanted to make waiting for my particular event deterministic and not wait indefinitely if there was a problem.
I chose to use a timer for this, and set it to fire only once, then stop the timer when it is no longer needed. Alternatively could leverage a CancellationToken here to manage the TaskCompletionSource but I feel this requires maintainers to know more about their usage and timers are more universally understood.
public static Task WhenDocumentCompleted(this WebBrowser browser, int timeoutInMilliseconds = 500)
{
var tcs = new TaskCompletionSource<bool>();
var timeoutTimer = new System.Timers.Timer(timeoutInMilliseconds);
timeoutTimer.AutoReset = false;
timeoutTimer.Elapsed += (s,e) => tcs.TrySetCanceled();
timeoutTimer.Start();
browser.DocumentCompleted += DocumentCompletedHandler;
return tcs.Task;
void DocumentCompletedHandler(object sender, EventArgs e)
{
timeoutTimer.Stop();
browser.DocumentCompleted -= DocumentCompletedHandler;
tcs.TrySetResult(true);
}
}
Note to ensure the code is thread safe I've gone more defensive here and used Try... functions. This ensures there are no errors setting the result even when edge-case interleaved execution occurs.

Related

Updating UI control after specific time elapsed

A WinForms application containing a custom control, LabelProgressBar : ProgressBar.
There is a method to make this control invisible (it works when called):
void statusIdle()
{
labelProgressBar1.Visible = false;
}
I need to make this control invisible (by calling the above method) a set amount of time after it has changed (calling other methods statusCompleted or statusFailed). For example:
void statusCompleted(string action)
{
// this is working
labelProgressBar1.Visible = true;
labelProgressBar1.Value = 100;
labelProgressBar1.setColourAndText(LabelProgressBarColours.WARNING_COLOUR, action + " Completed With Warnings");
// this is not
Timer timer = new Timer();
timer.Interval = 1000;
timer.AutoReset = false;
timer.Elapsed += new ElapsedEventHandler(timerElapsed);
timer.Start();
}
The event handler for the timer:
private void timerElapsed(object source, ElapsedEventArgs e)
{
statusIdle();
}
The control is not updating as required. What is the cause of this?
for one-time operation you can use async handler method and add delay:
async void statusCompleted(string action)
{
labelProgressBar1.Visible = true;
labelProgressBar1.Value = 100;
labelProgressBar1.setColourAndText(LabelProgressBarColours.WARNING_COLOUR, action + " Completed With Warnings");
await Task.Delay(1000);
statusIdle();
}
Your timer is a local variable of a function. When the function ends, it ends too. So you probably need to move the declaration somewhere else.

Unexpected behavior in async method

I am currently experiencing some unexpected/unwanted behavior with an aync method I am trying to use. The async method is RecognizeAsync. I am unabled to await this method since it returns void. What is happening, is that ProcessAudio method will be called first and will seemingly run to completion however the webpage never returns my "Contact" view as it should or errors out. After the method runs to completion, the breakpoints in my handlers start being hit. If I let it play through to completion, no redirect will ever happen- in the network tab in chrome debugger, the "status" will stay marked as pending and just hang there. I believe my issue is being caused by issues with asynchronousity but have been unable to found out what exactly it is.
All help is appreciated.
[HttpPost]
public async Task<ActionResult> ProcessAudio()
{
SpeechRecognitionEngine speechEngine = new SpeechRecognitionEngine();
speechEngine.SetInputToWaveFile(Server.MapPath("~/Content/AudioAssets/speechSample.wav"));
var grammar = new DictationGrammar();
speechEngine.LoadGrammar(grammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
return View("Contact", vm); //first breakpoint hit occurs on this line
//but it doesnt seem to be executed?
}
private void SpeechRecognizedHandler(object sender, EventArgs e)
{
//do some work
//3rd breakpoint is hit here
}
private void SpeechHypothesizedHandler(object sender, EventArgs e)
{
//do some different work
//2nd breakpoint is hit here
}
UPDATE: based on suggestions, I have changed my code to (in ProcessAudio):
using (speechEngine)
{
speechEngine.SetInputToWaveFile(Server.MapPath("~/Content/AudioAssets/speechSample.wav"));
var grammar = new DictationGrammar();
speechEngine.LoadGrammar(grammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
var tcsRecognized = new TaskCompletionSource<EventArgs>();
speechEngine.RecognizeCompleted += (sender, eventArgs) => tcsRecognized.SetResult(eventArgs);
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
try
{
var eventArgsRecognized = await tcsRecognized.Task;
}
catch(Exception e)
{
throw (e);
}
}
and this is resulting in some wrong behavior:
The return View("Contact",vm) breakpoint will now be hit AFTER the handlers are finished firing however there is still no redirect that ever happens. I am never directed to my Contact page. I just si ton my original page indefinitely just like before.
You're going too early. The speech engine probably hasn't even started by the time you hit the return View line.
You need to wait until the final event is fired from the speech engine. The best approach would be to convert from the event based asynchrony to TAP-based asynchrony.
This can be achieved by using TaskCompletionSource<T>
Let's deal with (what I believe) should be the last event to fire after speechEngine.RecognizeAsync is called, i.e. SpeechRecognized. I'm assuming that this is the event that fires when the final result has been calculated by the speech engine.
So, first:
var tcs = new TaskCompletionSource<EventArgs>();
now lets hook it up to complete when SpeechRecognized is fired, using inline lambda-style method declaration:
speechEngine.SpeechRecognized += (sender, eventArgs) => tcs.SetResult(eventArgs);
(...wait... what happens if no speech was recognized? We'll also need to hook up the SpeechRecognitionRejected event and define a custom Exception subclass for this type of event... here I'll just call it RecognitionFailedException. Now we're trapping all possible outcomes of the recognition process, so we would hope that the TaskCompletionSource would complete in all outcomes.)
speechEngine.SpeechRecognitionRejected += (sender, eventArgs) =>
tcs.SetException(new RecognitionFailedException());
then
speechEngine.RecognizeAsync(RecognizeMode.Multiple);
now, we can await the Task property of our TaskCompletionSource:
try
{
var eventArgs = await tcs.Task;
}
catch(RecognitionFailedException ex)
{
//this would signal that nothing was recognized
}
do some processing on the EventArgs that is the Task's result, and return a viable result back to the client.
In the process of doing this, you are creating IDisposable instances that will need to be properly disposed.
So:
using(SpeechRecognitionEngine speechEngine = new SpeechRecognitionEngine())
{
//use the speechEngine with TaskCompletionSource
//wait until it's finished
try
{
var eventArgs = await tcs.Task;
}
catch(RecognitionFailedException ex)
{
//this would signal that nothing was recognized
}
} //dispose
if anyone is curious- i solved my issue by doing the following:
I changed to using Recognize() instead of RecognizeAsync(..) which lead to InvalidOperationException due to async events trying to be executed at an "invalid time in the pages lifecycle". To overcome this, I wrapped my operations in a thread and joined the thread back to the main thread directly after running it. Code below:
using (speechEngine)
{
var t = new Thread(() =>
{
speechEngine.SetInputToWaveFile(#"C:\AudioAssets\speechSample.wav");
speechEngine.LoadGrammar(dictationGrammar);
speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognizedHandler);
speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(SpeechHypothesizedHandler);
speechEngine.Recognize();
});
t.Start();
t.Join();
}
}

Constant running process on a sperate thread blocking a UI thread

i am trying to use a third party telnet library "active expert" for a basic telnet session.
in my UI code behind i have something like
private async void Button_Click(object sender, RoutedEventArgs e)
{
var ts = new TelnetService();
await ts.DoConnect(node);
}
and my TelnetService looks like this
public class TelnetService
{
private Tcp objSocket = new Tcp();
private NwConstants objConstants = new NwConstants();
public string Responses { get; set; }
private Timer timer1 = new Timer();
public TelnetService()
{
timer1.Elapsed += timer1_Elapsed;
timer1.Interval = 100;
timer1.Start();
}
void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
public Task DoConnect(Node node)
{
return Task.Factory.StartNew(() =>
{
objSocket.Protocol = objConstants.nwSOCKET_PROTOCOL_TELNET;
objSocket.Connect(node.IP, 23);
while (true)
{
if ((Responses == null) || (!Responses.Contains(node.WaitString))) continue;
//do something
Responses = "";
break;
}
});
}
}
there are two important pieces of functionalities.
First in the timer1_Elapsed function which is process that will keeps on ruining and checks if there is data on socket, and if there is, it will append it to a string "Response". and i am using "timer" for it.
Second in the DoConnect function which will check the"Response" string for a certain input. for this i am using async await and Task.
in a nutshell first one accumulating the Response and Second one checking the Response.
Problem is that it looks like the timer code in general and
objSocket.ReceiveString()
line specifically is causing the UI thread to halt for several seconds. which means after clicking the button i cannot move my main form on the screen however the code is running in a separate thread.
i have tried using pure Thread for this but it didn't helped either.
update
instead of timer i am using a method AccumulateResponse
public static void AccumulateResponse()
{
while (true)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
}
and calling it like
var t = new Task(TelnetService.AccumulateResponse);
t.Start();
await TelnetService.DoConnect(node);
still no luck
The DoConnect isn't your problem. It is your Timer Elapsed Event handler.
The timer elapsed event is NOT asynchronous. Only the DoConnect is.
If there is no asynchronous version of ReceiveString() from your third party lib, then use Task.Run there as well inside of an async timer1_elapsed method.

Tracking when x number of events have fired

Usually this is the stuff i would spend a few hours browsing google and stackoverflow for, however i ran into the problem of how the heck do i word this for a search engine.
I hoping there is a simple way of achieving this, as my current method feels far to "hackish"
What I need to do, if track when several sources of data have completed their loading, and only when all have completed do i load a new view (this is WPF mvvm). Now the data is loaded via a static class termed Repository each one creates a thread and ensure they only a single load operation can happen at once (to avoid multiple threads trying to load into the same collection), each of these classes fires an event called LoadingCompleted when they have finished loading.
I have a single location that loads a large portion of the data (for the first time, there are other locations where the data is reloaded however) what i planned was to hook into each repositories OnLoaded event, and keep track of which have already returned, and when one is returned, mark it as loaded and check to see if any remain. If none remain load the new view, else do nothing.
Something like this:
ShipmentRepository.LoadingComplete += ShipmentRepository_LoadingComplete;
ContainerRepository.LoadingComplete += ContainerRepository_LoadingComplete;
void ContainerRepository_LoadingComplete(object sender, EventArgs e)
{
_containerLoaded = true;
CheckLoaded();
}
void ShipmentRepository_LoadingComplete(object sender, EventArgs e)
{
_shipmentsLoaded = true;
CheckLoaded();
}
private void CheckLoaded()
{
if(_shipmentsLoaded && _containersLoaded && _packagesLoaded)
{
LoadView();
}
}
However as i mentioned this feels clumbsy and hackish, I was hoping there was a cleaner method of doing this.
You can achieve this with Reactive Extensions and using Observable.FromEventPattern in conjunction with the Observable.Zip method. You should be able to do something like:
var shipmentRepositoryLoadingComplete = Observable.FromEventPattern<EventHandler,EventArgs>(h => ShipmentRepository.LoadingComplete += h, h => ShipmentRepository.LoadingComplete -= h);
var containerRepositoryLoadingComplete = Observable.FromEventPattern<EventHandler, EventArgs>(h => ContainerRepository.LoadingComplete += h, h => ContainerRepository.LoadingComplete -= h);
Then you subscibe to the observalbes like this:
var subscription = Observable.Zip(shipmentRepositoryLoadingComplete, containerRepositoryLoadingComplete)
.Subscribe(l => LoadView()));
The subscirption needs to stay alive, so you can save this as a private variable. When both complete events are invoked, the LoadView method should be called. Here is a working console example I used to test this method.
using System;
using System.Reactive.Linq;
namespace RxEventCombine
{
class Program
{
public event EventHandler event1;
public event EventHandler event2;
public event EventHandler event3;
public Program()
{
event1 += Event1Completed;
event2 += Event2Completed;
event3 += Event3Completed;
}
public void Event1Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 1 completed");
}
public void Event2Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 2 completed");
}
public void Event3Completed(object sender, EventArgs args)
{
Console.WriteLine("Event 3 completed");
}
static void Main(string[] args)
{
var program = new Program();
var event1Observable = Observable.FromEventPattern<EventHandler,EventArgs>(h => program.event1 += h, h => program.event1 -= h);
var event2Observable = Observable.FromEventPattern<EventHandler, EventArgs>(h => program.event2 += h, h => program.event2 -= h);
var event3Observable = Observable.FromEventPattern<EventHandler, EventArgs>(h => program.event3 += h, h => program.event3 -= h);
using (var subscription = Observable.Zip(event1Observable, event2Observable, event3Observable)
.Subscribe(l => Console.WriteLine("All events completed")))
{
Console.WriteLine("Invoke event 1");
program.event1.Invoke(null, null);
Console.WriteLine("Invoke event 2");
program.event2.Invoke(null, null);
Console.WriteLine("Invoke event 3");
program.event3.Invoke(null, null);
}
Console.ReadKey();
}
}
}
Output
Invoke event 1
Event 1 completed
Invoke event 2
Event 2 completed
Invoke event 3
Event 3 completed
All events completed
Another way to do this: Add a property LoadingCompleted. For every instance you start a thread return that object to a list. On every loadcompleted set the property to true and in the place you catch the load completed loop through the list (myList.Any(x=>LoadingCompleted == false)) to figure out if everything is completed.
Not the most correct way to do it. But reading your scenario this might do the job.
If you are loading the shipments, containers and packages as asynchronous task then you have several options. As others suggested you can use WhenAll or Join() to wait for all threads to finish before proceeding. However, if your threads have to stay alive and the threads don't stop when they have finished loading, you can use the System.Threading.CountdownEvent as following:
Edit
Added how I would set up the threads and handle the events. Also moved the example from the static Program to an instance, more closely resembeling your situation. Again, if you do not need to do anything in the threads after they have loaded the data, just skip the CountdownEvent altogether and wait for all threads to finish. Much simpler, does not need events and can be achieved using Join() or in this case Task.WaitAll().
class Program
{
static void Main(string[] args)
{
var myWpfObject = new MyWpfObject();
}
}
public class MyWpfObject
{
CountdownEvent countdownEvent;
public MyWpfObject()
{
ShipmentRepository ShipmentRepository = new ShipmentRepository();
ContainerRepository ContainerRepository = new ContainerRepository();
PackageRepository PackageRepository = new PackageRepository();
ShipmentRepository.LoadingComplete += Repository_LoadingComplete;
ContainerRepository.LoadingComplete += Repository_LoadingComplete;
PackageRepository.LoadingComplete += Repository_LoadingComplete;
Task[] loadingTasks = new Task[] {
new Task(ShipmentRepository.Load),
new Task(ContainerRepository.Load),
new Task(PackageRepository.Load)
};
countdownEvent = new CountdownEvent(loadingTasks.Length);
foreach (var task in loadingTasks)
task.Start();
// Wait till everything is loaded.
countdownEvent.Wait();
Console.WriteLine("Everything Loaded");
//Wait till aditional tasks are completed.
Task.WaitAll(loadingTasks);
Console.WriteLine("Everything Completed");
Console.ReadKey();
}
public void Repository_LoadingComplete(object sender, EventArgs e)
{
countdownEvent.Signal();
}
}
And a mock Repository class:
public class ShipmentRepository
{
public ShipmentRepository()
{
}
public void Load()
{
//Simulate work
Thread.Sleep(1000);
if (LoadingComplete != null)
LoadingComplete(this, new EventArgs());
Console.WriteLine("Finished loading shipments");
DoAditionalWork();
}
private void DoAditionalWork()
{
//Do aditional work after everything is loaded
Thread.Sleep(5000);
Console.WriteLine("Finished aditional shipment work");
}
public event EventHandler LoadingComplete;
}

Using async/await with void method

in my Windows Phone 8 application, I have a LoadData() method in my file MainViewModel.cs.
This method load data from a WCF service with entity framework...
Then, in my pages, I call LoadData()
The LoadData() method :
public void LoadData()
{
client.GetMoviesCompleted += new EventHandler<ServiceReference1.GetMoviesCompletedEventArgs>(client_GetMoviesCompleted);
client.GetMoviesAsync();
client.GetTheatersCompleted += new EventHandler<ServiceReference1.GetTheatersCompletedEventArgs>(client_GetTheatersCompleted);
client.GetTheatersAsync();
this.IsDataLoaded = true;
}
With the methods :
private void client_GetMoviesCompleted(object sender, ServiceReference1.GetMoviesCompletedEventArgs e)
{
Movies = e.Result;
}
private void client_GetTheatersCompleted(object sender, ServiceReference1.GetTheatersCompletedEventArgs e)
{
Theaters = e.Result;
}
Then in my pages :
App.ViewModel.LoadData();
The problem is that it doesn't wait until the data is loaded.
Can you help me to use Async/Await the LoadData() method to wait until the data is loaded ?
Thanks
So we'll start with these two methods that convert your existing methods from an event-based model into a task based model. You'll need to modify them slightly to line up with your types as I don't quite have enough information to replicate them completely, but the remaining change should be small:
public static Task<Movie[]> WhenGetMovies(MyClient client)
{
var tcs = new TaskCompletionSource<Movie[]>();
Action<object, Movie[]> handler = null;
handler = (obj, args) =>
{
tcs.SetResult(args.Result);
client.GetMoviesCompleted -= handler;
};
client.GetMoviesCompleted += handler;
client.GetMoviesAsync();
return tcs.Task;
}
public static Task<Theater[]> WhenGetMovies(MyClient client)
{
var tcs = new TaskCompletionSource<Theater[]>();
Action<object, Theater[]> handler = null;
handler = (obj, args) =>
{
tcs.SetResult(args.Result);
client.GetTheatersCompleted -= handler;
};
client.GetTheatersCompleted += handler;
client.GetTheatersAsync();
return tcs.Task;
}
Now that we can get tasks that represent the completion of these async operations loading the data is easy:
public async Task LoadData()
{
var moviesTask = WhenGetMovies(client);
var theatersTask = WhenGetTheaters(client);
var movies = await moviesTask;
var theaters = await theatersTask;
}
The problem is, when you execute your LoadData() method, the runtime don't wait to continue the execution of your method.
You can simply do a think like this :
private bool _moviesLoaded;
private bool _theatersLoaded;
private void client_GetMoviesCompleted(object sender, ServiceReference1.GetMoviesCompletedEventArgs e)
{
Movies = e.Result;
_moviesLoaded = true;
TrySetDataIsLoaded();
}
private void client_GetTheatersCompleted(object sender, ServiceReference1.GetTheatersCompletedEventArgs e)
{
Theaters = e.Result;
_theatersLoaded = true;
TrySetDataIsLoaded();
}
private void TrySetDataIsLoaded()
{
if(_moviesLoaded && _theatersLoaded) this.IsDataLoaded = true;
}
If you want to use async and await, you can try to work with TaskCompletionSource
The bottom line is that you'll need to design and implement a "loading" state for your application. This is true whether you use event-based asynchronous programming (like your current code) or async/await. You should not synchronously block the UI until the loading is complete.
Personally, I like to (synchronously) initialize everything into the "loading" state, and when the asynchronous loading completes, have it update data-bound items on the View Model. The View then transitions to a "ready" state via data binding.
make above two variables (private bool _moviesLoaded;private bool _theatersLoaded;) as properties and set them in completed eventhandlers . and till the set is called use loader and when set is called disable this loader and now you can use this data for your work..

Categories