为什么调用等待过早地完成父任务?

mad*_*dd0 1 .net c# async-await winrt-xaml

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

为此,我宣布了以下代表:

public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
Run Code Online (Sandbox Code Playgroud)

这允许消费者订阅该活动:

public event AsyncEventHandler<bool> DoLoading;
Run Code Online (Sandbox Code Playgroud)

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

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
                }
            };
Run Code Online (Sandbox Code Playgroud)

在适当的时间点,我正在获取TaskSchedulerUI线程并存储它_uiScheduler.

适当时由loader以下行触发事件(这发生在随机线程中):

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

请注意,此行不是从UI线程调用的,但需要在加载完成时更新UI,因此我ContinueWith在加载任务完成时用于在UI任务调度程序上执行代码.

我已经尝试了以下方法的几种变体,其中没有一种方法有效,所以这就是我所处的位置:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我正在开始执行两项任务,并希望我ContinueWith之前完成所有这些任务.

commandTask完成立即,所以可以暂时忽略.的eventHandlerTask,在我看来,应该只完成一个事件处理程序完成的,因为我在等待呼叫调用的事件处理程序的方法,我在等待事件处理程序本身.

然而,实际发生的是,只要await Task.Delay(1000)我的事件处理程序中的行被执行,任务就会完成.

为什么这样,我怎么能得到我期望的行为?

svi*_*ick 8

你正确地意识到在这种情况下StartNew()返回Task<Task>,你关心内部Task(虽然我不确定你为什么Task在开始之前等待外部commandTask).

但是你回来Task<Task>并忽略内心Task.你应该做的是使用await替代return和改变的返回类型PerformLoadingActionAsync()只是Task:

await Task.WhenAll(evenHandlerTask, commandTask);
Run Code Online (Sandbox Code Playgroud)

几个笔记:

  1. 使用事件处理程序这种方式非常危险,因为您关心Task从处理程序返回的内容,但如果有更多处理程序,则只有在Task正常引发事件时才会返回最后一个处理程序.如果你真的想这样做,你应该调用GetInvocationList(),它允许你分别调用和await处理每个处理程序:

    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);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    如果您知道您将永远不会有多个处理程序,则可以使用可以直接设置而不是事件的委托属性.

  2. 如果你有一个async方法或lambda只有await它之前return(并且没有finallys),那么你不需要创建它async,只需Task直接返回:

    Task.Factory.StartNew(() => this.OnDoLoading(true))
    
    Run Code Online (Sandbox Code Playgroud)