为什么呼叫等待过早完成父任务? [英] Why is calling await completing the parent Task prematurely?

查看:113
本文介绍了为什么呼叫等待过早完成父任务?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个控件,该控件公开一个 DoLoading 事件,消费者可以订阅该事件以执行加载操作。为了方便起见,应该从UI线程中调用事件处理程序,以允许使用者随意更新UI,但是他们也将能够使用async / await执行长时间运行的任务而不会阻塞UI线程。

I'm trying to create a control that exposes a DoLoading event that consumers can subscribe to in order to perform loading operations. For convenience, event handlers should be called from the UI thread allowing consumers to update the UI at will, but they will also be able to use async/await to perform long-running tasks without blocking the UI thread.

为此,我声明了以下代表:

For this, I have declared the following delegate:

public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);

允许消费者订阅该事件:

That allows consumers to subscribe to the event:

public event AsyncEventHandler<bool> DoLoading;

这个想法是消费者将订阅事件(此行在UI线程中执行) ):

The idea is that consumers will subscribe to the event as so (this line is executed in the UI thread):

loader.DoLoading += async (s, e) =>
            {
                for (var i = 5; i > 0; i--)
                {
                    loader.Text = i.ToString(); // UI update
                    await Task.Delay(1000); // long-running task doesn't block UI
                }
            };

在适当的时间点,我会得到 TaskScheduler 用于UI线程并将其存储在 _uiScheduler 中。

At an appropriate point in time, I'm getting a TaskScheduler for the UI thread and storing it in _uiScheduler.

该事件在适当的时候由 loader 并包含以下行(这发生在随机线程中):

The event is triggered when appropriate by the loader with the following line (this happens in a random thread):

this.PerformLoadingActionAsync().ContinueWith(
            _ =>
            {
                // Other operations that must happen on UI thread
            },
            _uiScheduler);

请注意,该行不是从UI线程调用的,但在加载完成后需要更新UI ,所以我使用 ContinueWith 在加载任务完成时在UI任务计划程序上执行代码。

Notice that this line is not called from the UI thread but needs to update the UI when loading is completed, so I'm using ContinueWith to execute code on the UI task scheduler when the loading task completes.

I已经尝试了以下方法的几种变体,但没有一种起作用,所以我在这里:

I've tried several variations of the following methods, none of which have worked, so here's where I'm at:

private async Task<Task> PerformLoadingActionAsync()
{
    TaskFactory uiFactory = new TaskFactory(_uiScheduler);

    // Trigger event on the UI thread and await its execution
    Task evenHandlerTask = await uiFactory.StartNew(async () => await this.OnDoLoading(_mustLoadPreviousRunningState));

    // This can be ignored for now as it completes immediately
    Task commandTask = Task.Run(() => this.ExecuteCommand());

    return Task.WhenAll(evenHandlerTask, commandTask);
}

private async Task OnDoLoading(bool mustLoadPreviousRunningState)
{
    var handler = this.DoLoading;

    if (handler != null)
    {
        await handler(this, mustLoadPreviousRunningState);
    }
}

如您所见,我正在开始两个任务并希望我的 ContinueWith 能够完成其中的一个全部

As you can see, I'm starting two tasks and expect my ContinueWith from before to execute one all of them complete.

commandTask 立即完成,因此暂时可以忽略。正如我所见, eventHandlerTask 应该只完成一个事件处理程序完成,因为我正在等待调用该事件处理程序的方法的调用,

The commandTask completes immediately, so it can be ignored for the moment. The eventHandlerTask, as I see it, should only complete one the event handler completes, given that I'm awaiting the call to the method that calls the event handler and I'm awaiting the event handler itself.

但是,实际上正在发生的事情是,只要在 await Task.Delay( 1000)在我的事件处理程序中被执行。

However, what's actually happening, is that the tasks are being completed as soon as the line await Task.Delay(1000) in my event handler is executed.

这是为什么,如何获得期望的行为?

Why is this and how can I get the behaviour I expect?

推荐答案

您正确地意识到 StartNew()返回 Task< Task> 在这种情况下,您会关心内部的 Task (尽管我不确定您为什么要等待外部的 Task ,然后再启动 commandTask )。

You correctly realized that StartNew() returns Task<Task> in this case, and you care about the inner Task (though I'm not sure why are you waiting for the outer Task before starting commandTask).

但随后返回 Task< Task> 并忽略内部的 Task 。您应该做的是使用 await 代替 return 并更改 PerformLoadingActionAsync的返回类型() Task

But then you return Task<Task> and ignore the inner Task. What you should do is to use await instead of return and change the return type of PerformLoadingActionAsync() to just Task:

await Task.WhenAll(evenHandlerTask, commandTask);

更多说明:


  1. 以这种方式使用事件处理程序非常危险,因为您关心的是从处理程序返回的 Task ,但是如果有更多处理程序,则只有如果正常引发事件,将返回最后一个 Task 。如果确实要执行此操作,则应调用 GetInvocationList() ,它使您可以分别调用和 await 每个处理程序:

  1. Using event handlers this way is quite dangerous, because you care about the Task returned from the handler, but if there are more handlers, only the last Task will be returned if you raise the event normally. If you really want to do this, you should call GetInvocationList(), which lets you invoke and await each handler separately:

private async Task OnDoLoading(bool mustLoadPreviousRunningState)
{
    var handler = this.DoLoading;

    if (handler != null)
    {
        var handlers = handler.GetInvocationList();

        foreach (AsyncEventHandler<bool> innerHandler in handlers)
        {
            await innerHandler(this, mustLoadPreviousRunningState);
        }
    }
}

如果您知道自己永远不会有多个处理程序,您可以使用可以直接设置的委托属性来代替事件。

If you know that you'll never have more than one handler, you could use a delegate property that can be directly set instead of an event.

如果您有异步方法或lambda,在返回 之前只有一个等待没有最终 s),那么您就不需要使其<< c $ c>异步,只需返回任务直接:

If you have an async method or lambda that has the only await just before its return (and no finallys), then you don't need to make it async, just return the Task directly:

Task.Factory.StartNew(() => this.OnDoLoading(true))


这篇关于为什么呼叫等待过早完成父任务?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆