1

I have a WebBrowser control and it has InvokeScript method, which you should call only after WebBrowser is loaded.

So I've tried something like this:

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());
}          

It does not look very nice to me, but works ok when called asynchronously. Problem appears, when I try to read result value synchronously - app just hangs:

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

I know, that I should go all the way async, but I'm wondering what to do with properties:

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

Or async is wrong way to go in this case at all?

Update

Property is a 'glue' between input field value in browser's page and view model in WPF, so I don't see a good way to make it as separate methods, especially because it is a part of the bigger framework (notice override keyword on it). Once browser control is loaded, execute logic should not take long, I guess less than 10 milliseconds, that is why I would be ok with sync execution in this case. And usually browser control loads fast enough, the only reason here to delay is to make sure InvokeScript is not called before load, not because it taking long time or smth.

Giedrius
  • 8,430
  • 6
  • 50
  • 91
  • I try to avoid logic in my getters and setters if at all possible. Using async methods in a getter leaves things floating in limbo - it might return now, or in a minute, or in an hour, etc. To avoid the situation you're describing above, I would normally try to trigger any necessary logic *before* I need the data from the getter. Can you provide a little more context around the use of the UserText property, and I'll see if I can figure out where I'd put the call to the SetEnteredText method? – goobering Apr 29 '15 at 09:00
  • [Here](http://stackoverflow.com/a/21152965/1768303) you'll find how to handle `onload` event using `async`/`await`. [This](http://stackoverflow.com/a/22262976/1768303) as well might be of your interest. – noseratio Apr 29 '15 at 09:30
  • @goobering - I've explained usage. – Giedrius Apr 29 '15 at 10:16

1 Answers1

3

app just hangs

We'll, you said it yourself. You know why that happens. You're blocking on the call using Task.Result, that is what causes the deadlock.

Or async is wrong way to go in this case at all?

We don't have async properties. The reason we don't have them are because properties aren't asynchronous by nature. Stephan Cleary describes it nicely:

If your “property” needs to be asynchronously evaluated every time it’s accessed, then you’re really talking about an asynchronous operation.The best solution is to change the property to an async method. Semantically, it shouldn’t be a property.

Instead of a property, make it an asynchronous method which you can await properly.

In regards to using a ManualResetEvent, I would use a TaskCompletionSource<bool> instead. It works "nicer" with 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)
    });
}

I also see that you used TaskScheduler.FromCurrentSynchronizationContext() in your continuation. If you need this to execute on the UI thread, there is no point in using a threadpool thread at all.

@Noseratio adds that WebBrowser is an STA object. His answer here might help.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Yuval, the problem here is that you cannot really use `browser` from `Task.Run`, as `WebBrowser` is an STA object and can only be accessed from the thread which has created it. Perhaps, the OP is looking for something like [this](http://stackoverflow.com/a/21152965/1768303). – noseratio Apr 29 '15 at 09:27
  • @Noseratio Thanks, I didn't know that, as i didn't get into the semantics of the execution of the delegate he's using. – Yuval Itzchakov Apr 29 '15 at 09:57
  • 1
    @Noseratio Edited to clarify :) – Yuval Itzchakov Apr 29 '15 at 09:59
  • I guess there's deadlock, but not sure why, trying to debug doesn't reveal much. I'll take closer look to @Noseratio answer to see if it fits my needs. – Giedrius Apr 29 '15 at 10:21
  • 1
    @Giedrius You have a deadlock because you're block with `Task.Result`. I thought you understood that. – Yuval Itzchakov Apr 29 '15 at 10:32