具有已完成事件的异步方法
本文关键字:异步方法 事件 已完成 | 更新日期: 2023-09-27 17:58:53
我使用.net 4.0,并试图找出如何使用异步方法等待DocumentCompleted事件完成并返回值。我的原始代码如上所述,在这种情况下,我如何将其转换为异步/等待模型?
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);
}
因此,我们需要一个在DocumentCompleted
事件触发时返回任务的方法。任何时候,对于给定的事件,您都可以创建这样的方法:
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
return tcs.Task;
}
一旦你有了,你可以使用:
await browser.WhenDocumentCompleted();
@Servy有我想要的天才答案,但它不太适用于我的用例。当事件被多次引发时,我发现了错误,因为事件处理程序试图在后续事件调用的TaskCompletionSource
上设置结果。
我从两个方面加强了他的回答。第一种方法是在第一次处理DocumentCompleted
事件后取消订阅。
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);
}
}
注意,我在这里使用了一个本地函数来捕获
TaskCompletionSource
实例,它至少需要C#7.0。
第二个增强功能是添加超时。我的特定用例是在单元测试中,我想让等待我的特定事件具有确定性,而不是在出现问题时无限期地等待。
我选择使用计时器,并将其设置为只启动一次,然后在不再需要计时器时停止计时器。或者,可以在这里使用CancellationToken
来管理TaskCompletionSource
,但我觉得这需要维护人员更多地了解它们的用法,并且计时器更容易被普遍理解。
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);
}
}
注意,为了确保代码是线程安全的,我在这里采取了更多的防御措施,并使用了
Try...
函数。这确保了即使在发生边缘情况交错执行时,也不会出现设置结果的错误。