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
@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();
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();
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.