如何推迟执行直到某些事件发生?

时间:2015-04-29 08:41:58

标签: c# wpf async-await

我有一个WebBrowser控件,它有InvokeScript方法,只有在加载WebBrowser后才能调用它。

所以我尝试过这样的事情:

private readonly ManualResetEventSlim browserLoaded = new ManualResetEventSlim(false);    

private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{                   
        browserLoaded.Set();
}

private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
        return await Task.Factory
            .StartNew(() =>
                      {
                          if (!browserLoaded.Wait(TimeSpan.FromSeconds(10)))
                          {
                              throw new Exception("Timeout for waiting browser to load.");
                          }
                      })
            .ContinueWith(task => parameters == null
                ? browser.InvokeScript(invoke)
                : browser.InvokeScript(invoke, parameters), TaskScheduler.FromCurrentSynchronizationContext());
}          

对我来说看起来不是很好,但是在异步调用时工作正常。 当我尝试同步读取结果值时出现问题 - 应用程序挂起:

private string GetEnteredText() 
{
     return (string)InvokeScript("getEnteredText").Result;
}

我知道,我应该一路走异步,但我想知道如何处理属性:

public override string UserText
{
    get
    {
        return GetEnteredText();
    }
    set
    {
        SetEnteredText(value);
    }
}

或者async在这种情况下是错误的方法吗?

更新

属性是浏览器页面中输入字段值与WPF中视图模型之间的“粘合剂”,因此我没有看到将其作为单独方法的好方法,特别是因为它是更大框架的一部分(通知覆盖关键字)。 加载浏览器控件后,执行逻辑不应该花费很长时间,我想小于10毫秒,这就是为什么在这种情况下我可以使用同步执行。通常浏览器控件加载速度足够快,这里延迟的唯一原因是确保在加载之前不调用InvokeScript,而不是因为它需要很长时间或smth。

1 个答案:

答案 0 :(得分:3)

  

app挂起

我们,你自己说的。你知道为什么会这样。您使用Task.Result阻止了通话,这就是导致死锁的原因。

  

或者async在这种情况下是错误的方法吗?

我们没有异步属性。我们没有它们的原因是因为属性本​​质上不是异步的。 Stephan Cleary describes it nicely

  

如果您的“财产”需要每次都进行异步评估   它被访问,然后你真的在谈论异步   最佳解决方案是将属性更改为异步   方法。在语义上,它不应该是属性。

而不是属性,使其成为一个异步方法,您可以await正确。

关于使用ManualResetEvent,我会改用TaskCompletionSource<bool>。它运作良好&#34;更好&#34;与TPL:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{                   
    tcs.TrySetResult(true);
}

private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
    var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));
    if (timeoutTask == await Task.WhenAny(tcs.Task, timeoutTask))
    {
        // You've timed out;
    }

    return Task.Run(() =>
    {
         parameters == null ? browser.InvokeScript(invoke)
                            : browser.InvokeScript(invoke, parameters)
    });
}

我也看到你在继续中使用了TaskScheduler.FromCurrentSynchronizationContext()。如果你需要在UI线程上执行它,那么根本不需要使用线程池线程。

@Noseratio补充说WebBrowser是一个STA对象。他的回答here可能有所帮助。