RunAsync – 如何等待UI线程上的工作完成?
等待Dispatcher.RunAsync
时,将在计划工作时进行继续,而不是在工作完成时进行。 我怎么能等待完成的工作?
编辑
我的原始问题假设过早延续是由API的设计引起的,所以这是真正的问题。
在使用异步委托等待Dispatcher.RunAsync
,在委托代码中使用await
时,会在遇到await
时发生延续,而不是在工作完成时。 我怎么能等待完成的工作?
编辑2
您可能需要分派已经在UI线程上工作的一个原因是解决细微的时序和布局问题。 对于视觉树中的元素的大小和位置的值来说,通常是非常常见的,并且用于稍后的UI迭代的调度工作可以帮助。
我在Microsoft github存储库上找到以下建议:如何等待从后台线程发送的UI任务 。
建立
为CoreDispatcher
定义此扩展方法:
using System; using System.Threading.Tasks; using Windows.UI.Core; public static class DispatcherTaskExtensions { public static async Task RunTaskAsync (this CoreDispatcher dispatcher, Func> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { var taskCompletionSource = new TaskCompletionSource (); await dispatcher.RunAsync(priority, async () => { try { taskCompletionSource.SetResult(await func()); } catch (Exception ex) { taskCompletionSource.SetException(ex); } }); return await taskCompletionSource.Task; } // There is no TaskCompletionSource so we use a bool that we throw away. public static async Task RunTaskAsync(this CoreDispatcher dispatcher, Func func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) => await RunTaskAsync(dispatcher, async () => { await func(); return false; }, priority); }
完成后,您需要做的就是使用新的RunTaskAsync
方法让您的后台任务等待UI工作。
用法示例
让我们假装这是需要在UI线程中运行的方法。 注意调试语句,这将有助于遵循流程:
public static async Task ShowMessageAsync() { // Set up a MessageDialog var popup = new Windows.UI.Popups.MessageDialog("Question", "Please pick a button to continue"); popup.Commands.Add(new Windows.UI.Popups.UICommand("Button 1")); popup.Commands.Add(new Windows.UI.Popups.UICommand("Button 2")); popup.CancelCommandIndex = 0; // About to show the dialog Debug.WriteLine("Waiting for user choice..."); var command = await popup.ShowAsync(); // Dialog has been dismissed by the user Debug.WriteLine("User has made a choice. Returning result."); return command.Label; }
要从后台线程等待,您可以使用RunTaskAsync
:
// Background thread calls this method public async void Object_Callback() { Debug.WriteLine("Object_Callback() has been called."); // Do the UI work, and await for it to complete before continuing execution var buttonLabel = await Dispatcher.RunTaskAsync(ShowMessageAsync); Debug.WriteLine($"Object_Callback() is running again. User clicked {buttonLabel}."); }
然后输出如下所示:
已调用Object_Callback()。
等待用户选择……
用户做出了选择。 返回结果。
Object_Callback()再次运行。 用户点击了按钮1。
您的问题是假设您要从后台线程安排(并等待)UI线程上的工作。
如果您将UI作为“主”并且后台线程是“从属”,您通常会发现您的代码更清晰,更容易理解(并且它肯定会更加便携)。
因此,不是让后台线程await
UI线程做一些操作(使用笨拙和不可移植的Dispatcher.RunAsync
),你将让UI线程await
后台线程做的一些操作(使用便携式,制作Task.Run
-async Task.Run
)。
您可以在您自己的异步方法RunAsync
的调用包装起来,并控制任务的完成,从而继续等待调用者自己。
由于async-await以Task
类型为中心,因此您必须使用此类型编排工作。 但是,通常Task
调度自身在线程池线程上运行,因此它不能用于调度UI工作。
但是, TaskCompletionSource
类型被发明为一种不定期Task
的木偶TaskCompletionSource
。 换句话说, TaskCompletionSource
可以创建一个未安排执行任何操作的虚拟Task
,但是通过TaskCompletionSource
上的方法可以看起来像正常作业一样运行和完成。
看这个例子。
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; }
注意:在UI线程上完成的主要工作只是每隔100ms在屏幕上绘制一些线条。
TaskCompletionSource
被创建为puppet master。 看看接近结尾,你会看到它有一个返回给调用者的Task
属性。 返回Task
满足编译器需求并使方法等待和异步。
但是, Task
只是一个木偶,是UI线程中实际工作的代理。
看看在主UI TaskCompletionSource.SetResult
我如何使用TaskCompletionSource.SetResult
方法将结果强制执行到Task
(因为返回给调用者)并传达该工作已经完成。
如果出现错误,我使用SetException
“拉出另一个字符串”并使其看起来在puppet Task
出现了exception。
async-await子系统没有什么不同,因此它可以按照您的预期运行。
编辑
根据svick的提示,如果该方法只能从UI线程调用,那么这就足够了:
/// /// 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); } } } }
干净的方式工作的好方法@StephenCleary建议即使你出于某种原因必须从工作线程开始,也就是使用一个简单的帮助对象。 使用下面的对象,您可以编写如下代码:
await DispatchToUIThread.Awaiter; // Now you're running on the UI thread, so this code is safe: this.textBox.Text = text;
在你的App.OnLaunched中你必须初始化对象:
DispatchToUIThread.Initialize(rootFrame.Dispatcher);
下面的代码背后的理论,您可以在等待任何事情;
上述就是C#学习教程:RunAsync – 如何等待UI线程上的工作完成?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
public class DispatchToUIThread : INotifyCompletion { private readonly CoreDispatcher dispatcher; public static DispatchToUIThread Awaiter { get; private set; } private DispatchToUIThread(CoreDispatcher dispatcher) { this.dispatcher = dispatcher; } [CLSCompliant(false)] public static void Initialize(CoreDispatcher dispatcher) { if (dispatcher == null) throw new ArgumentNullException("dispatcher"); Awaiter = new DispatchToUIThread(dispatcher); } public DispatchToUIThread GetAwaiter() { return this; } public bool IsCompleted { get { return this.dispatcher.HasThreadAccess; } } public async void OnCompleted(Action continuation) { if (continuation == null) throw new ArgumentNullException("continuation"); await this.dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => continuation()); } public void GetResult() { } }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/953068.html