Reputation: 8540
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.
Upvotes: 1
Views: 191
Reputation: 149518
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.
Upvotes: 3