4
private async Task<T> LoadForm(WebControlAsync browser, Uri url)
{ ... }

var forms = await await _dispatcher.InvokeAsync(async () => await LoadForm(browser, form.Url));

I don't understand why I have to use two await's here to get T in forms? So it looks as InvokeAsync returns Task<Task<T>>. But when I invoke synchronous method like this:

var forms = await _dispatcher.InvokeAsync(() => FillForm(forms, url));

it requires only single await. So the cause seems to be async lambda. I understand that if I write it this way:

var forms = await await _dispatcher.InvokeAsync(() => LoadForm(browser, form.Url));

then the return type of LoadForm is Task<T> and InvokeAsync returns Task<lambda return type> so it indeed would be Task<Task<T>>. But when a Task method is await'ed, isn't it "unwraps" the actual return type from Task? So if I write:

var forms = await LoadForm(browser, form.Url);

forms would be T, not Task<T>. Why the same isn't happen in async lambda?

Aleksey Shubin
  • 1,960
  • 2
  • 20
  • 39

1 Answers1

9

You've answered your own question. InvokeAsync returns a Task<T> where T is the return type of the Func<TResult> delegate supplied to it. When you use an async lambda, you are no longer dealing with Func<TResult>, but instead Func<Task<TResult>>.

The reason you think that unwrapping should happen automatically is probably due to using Task.Run in the past. It should be noted, however, that Task.Run has an overload which accepts a Func<T> and returns a Task<T>, and an overload which accepts a Func<Task<T>>, and still returns Task<T>, so you take this unwrapping which is happening for you behind the covers for granted. This, however, does not apply in your case.

Dispatcher.InvokeAsync is similar to Task.Factory.StartNew in this regard. It does not have an overload which would specifically deal with Func<Task<TResult>>, so you're stuck with unwrapping "manually".

Thing is, think about whether you really need InvokeAsync in your scenario. It won't give you much outside of scenarios where the dispatcher thread is overloaded with work. The Task which it returns will generally complete immediately (without awaiting the Task created by your async lambda) and just give you one more layer of wrapping to deal with. LoadForm is already async so you might as well just use Dispatcher.Invoke which will fire off your async lambda on the correct SynchronizationContext and ultimately return the task that you want to await, or, if you know that your LoadForm will always be called with the right SynchronizationContext (that is, on the UI thread), omit the dispatcher calls altogether and just let async/await do its thing.

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • I knew I did something wrong. :) Indeed there is no need in `InvokeAsync` in this case, `Invoke` will do what I want (I still need it because `LoadForm` will called from non-UI thread). Thanks! – Aleksey Shubin Jul 13 '14 at 02:42
  • 2
    @AlekseyShubin, what you did wasn't wrong in the true sense of the word. It just leads to slightly unconventional syntax that's harder to wrap your head around. When you absolutely cannot wait for the dispatcher thread to kick off your `FillForm` task via `Invoke` (which itself is a blocking operation that can take time if your dispatcher is busy), using `Dispatcher.InvokeAsync` is a valid choice, even if it does lead to funny syntax by forcing you to use a double `await` or `Unwrap` when combined with `async` (or simply `Task`-returning) delegates. – Kirill Shlenskiy Jul 13 '14 at 03:04