简体   繁体   中英

await inside a WPF Dispatcher call

What is the best solution to ensure errorhandling when awating inside a delegate that is pased to the dispatcher?

In short: I need to return a number after parsing a calculation to Foo. That calculation is very slow and have to be done on the UI thread. The calculation must be handed to another class as a Task<string> .

The only solution I could come up with was CallFooWithATask___3 - and not even sure about that..

public class Foo
{
    public void CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(Task<string> task) { }
}

// CallFooWithATask___ is invoked from unknown thread. Can't wait for GetWithSideEffects on calling thread
public class SomeClass
{
    private TextBox textBox;

    public int CallFooWithATask___1(Foo foo)
    {
        // not good - async void -> no error handling
        var tcs = new TaskCompletionSource<string>();
        Dispatcher.CurrentDispatcher.BeginInvoke(async () =>
        {                
            var a = await GetWithSideEffects();
            textBox.Text = a;
            tcs.SetResult(a);
        });

        // quite fast - probally put it in a queue and returns
        foo.CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(tcs.Task);

        return 1;
    }

    public async Task<int> CallFooWithATask___2(Foo foo)
    {
        // not good - still async void  -> no error handling .. when is foo actually called? I assume when hitting the inner await'ish?
        var task =  await Dispatcher.CurrentDispatcher.InvokeAsync(async () =>
        {
            var a = await GetWithSideEffects();
            textBox.Text = a;
            return a;
        });

        // quite fast - probally put it in a queue and returns
        foo.CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(task);

        return 1;
    }

    public int CallFooWithATask___3(Foo foo)
    {
        // what is the elegant solution - probally not this?
        var tcs = new TaskCompletionSource<string>();
        Dispatcher.CurrentDispatcher.BeginInvoke(async () =>
        {
            try
            {
                var a = await GetWithSideEffects();
                textBox.Text = a;
                tcs.SetResult(a);
            }
            catch (Exception ex) { tcs.SetException(ex); }
        });

        // quite fast - probally put it in a queue and returns
        foo.CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(tcs.Task);

        return 1;
    }

    // this might trigger ui updates and is very slow ..
    private Task<string> GetWithSideEffects()=> Task.FromResult("42");
}

You're pretty close. Just extract your async code in a method or in a Func<Task<string>> to avoid ending with an async void :

Func<Task<string>> func = async () =>
{
    var a = await GetWithSideEffects();

    return a;
};

Then invoke it with InvokeAsync . You'll end up with a Task<Task<string>> . The inner task is the one returned by your async method, the outer task is the one generated by InvokeAsync to indicate when the call is actually dispatched. Use .Unwrap to merge those tasks, and finally send them to your other method:

var task = Dispatcher.InvokeAsync(func).Task.Unwrap();

foo.CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(task);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM