RunAsync - How do I await the completion of work on the UI thread?

前端 未结 4 699
野趣味
野趣味 2020-12-05 08:42

When awaiting Dispatcher.RunAsync the continuation occurs when the work is scheduled, not when the work has completed. How can I await the work completing?

4条回答
  •  北海茫月
    2020-12-05 09:02

    You can wrap the call to RunAsync in your own asynchronous method that can be awaited and control the completion of the task and thus the continuation of awaiting callers yourself.

    Since async-await is centred on the Task type, you must orchestrate the work using this type. However, usually a Task schedules itself to run on a threadpool thread and so it cannot be used to schedule UI work.

    However, the TaskCompletionSource type was invented to act as a kind of puppeteer to an unscheduled Task. In other words, a TaskCompletionSource can create a dummy Task that is not scheduled to do anything, but via methods on the TaskCompletionSource can appear to be running and completing like a normal job.

    See this example.

    public Task PlayDemoAsync()
    {
        var completionSource = new TaskCompletionSource();
        this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
        {
            try
            {
                foreach (var ppc in this.Plots.Select(p => this.TransformPlot(p, this.RenderSize)))
                {
                    // For each subsequent stroke plot, we need to start a new figure.
                    //
                    if (this.Sketch.DrawingPoints.Any())
                        this.Sketch.StartNewFigure(ppc.First().Position);
    
                    foreach (var point in ppc)
                    {
                        await Task.Delay(100);
    
                        this.Sketch.DrawingPoints.Add(point.Position);
                    }
                }
    
                completionSource.SetResult(true);
            }
            catch (Exception e)
            {
                completionSource.SetException(e);
            }
        });
    
        return (Task)completionSource.Task;
    }
    

    Note: the main work being done on the UI thread is just some lines being drawn on screen every 100ms.

    A TaskCompletionSource is created as the puppet master. Look near the end and you'll see that it has a Task property that is returned to the caller. Returning Task satisfies the compilers needs and makes the method awaitable and asynchronous.

    However, the Task is just a puppet, a proxy for the actual work going on in the UI thread.

    See how in that main UI delegate I use the TaskCompletionSource.SetResult method to force a result into the Task (since returned to the caller) and communicate that work has finished.

    If there's an error, I use SetException to 'pull another string' and make it appear that an exception has bubbled-up in the puppet Task.

    The async-await subsystem knows no different and so it works as you'd expect.

    Edit

    As prompted by svick, if the method was designed to be callable only from the UI thread, then this would suffice:

        /// 
        /// Begins a demonstration drawing of the asterism.
        /// 
        public async Task PlayDemoAsync()
        {
            if (this.Sketch != null)
            {
                foreach (var ppc in this.Plots.Select(p => this.TransformPlot(p, this.RenderSize)))
                {
                    // For each subsequent stroke plot, we need to start a new figure.
                    //
                    if (this.Sketch.DrawingPoints.Any())
                        this.Sketch.StartNewFigure(ppc.First().Position);
    
                    foreach (var point in ppc)
                    {
                        await Task.Delay(100);
    
                        this.Sketch.DrawingPoints.Add(point.Position);
                    }
                }
            }
        }
    

提交回复
热议问题