ждите внутри вызова диспетчера WPF - PullRequest
0 голосов
/ 26 февраля 2020

Какое лучшее решение для обеспечения обработки ошибок при ожидании внутри делегата, переданного диспетчеру?

Короче говоря: мне нужно вернуть число после анализа вычисления в Foo. Это вычисление очень медленно и должно быть сделано в потоке пользовательского интерфейса. Вычисление должно быть передано другому классу как Task<string>.

Единственное решение, которое я мог придумать, было CallFooWithATask___3 - и даже не уверен в этом ..

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

1 Ответ

0 голосов
/ 26 февраля 2020

Ты довольно близко. Просто извлеките свой асиновый c код в методе или в Func<Task<string>>, чтобы избежать окончания async void:

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

    return a;
};

Затем вызовите его с помощью InvokeAsync. Вы получите Task<Task<string>>. Внутренняя задача - это задача, возвращаемая вашим асинхронным методом c, а внешняя задача - это задача, генерируемая InvokeAsync для указания того, когда вызов фактически отправлен. Используйте .Unwrap для объединения этих задач и, наконец, отправьте их другим способом:

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

foo.CallMeWithATaskThatIsFinishedWhenTheUIIsUpdated(task);
...