如何在线程安全的方法中调用 Dispatcher.RunAsync() 来更新 UI 组件 [英] How to call Dispatcher.RunAsync() in a thread safe method to update UI components

查看:33
本文介绍了如何在线程安全的方法中调用 Dispatcher.RunAsync() 来更新 UI 组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,经过深思熟虑,我认为我的标题不正确.

但是,我一直试图找到一种方法来更新从 Task.Run(async ()=>{ }) 表达式/方法产生的多个线程的 UI 控件.我正在 UWP windows mobile 10 应用程序中执行此操作.

我已经列出了大部分涉及的例程,需要帮助来完成对 DisplayProgress(string message) 方法的调用.在此方法中,我想使用 Dispathcer.RunAsync() 以线程安全的方式更新 UI 控件,但需要它是线程安全的.请参阅下面代码中的尝试 1&2.

公共密封部分类 SynchProcess : BasePage{protected override void OnNavigatedTo(NavigationEventArgs e){Task.Run(async()=>{var 结果 = await SynchTables();});}私有异步任务同步表(){布尔 bRet = 真;列表<任务>任务 = 新列表<任务>();尝试{//刷新 1tasks.Add(Task.Run(async () =>{//更新界面DisplayProgress(汽车刷新……正在运行");列表<汽车>汽车 = 等待 _dataService.GetCarsData();DataAccessSQLite.DeleteAll();DataAccessSQLite.InsertCars(cars);//更新界面DisplayProgress(汽车刷新……完成");}));//刷新 2tasks.Add(Task.Run(async () =>{//更新界面DisplayProgress("Tracks refresh…running");列表<曲目>轨道 = 等待 _dataService.GeTrackstData();DataAccessSQLite.DeleteAll();DataAccessSQLite.InsertTracks(tracks);//更新界面DisplayProgress(曲目刷新……完成");}));//刷新 3//刷新 4……Task.WaitAll(tasks.ToArray());}Catch(AggreggateException agEx){bRet = 假;//为简洁起见删除…}返回 bRet;}//尝试 1 :这是我知道的,但为了完整性而包括在内.//失败 - await 操作符只能在异步方法中使用.私有静态无效显示进度(字符串消息){锁(_lockObject){await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{//更新控制逻辑;});}//尝试 2//失败 - 非静态字段、方法或//属性DependencyObject.Dispatcher"私有静态异步任务 DisplayProgress(字符串消息){await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>{//这里更新UI逻辑});}}

我发现这篇文章 http://briandunnington.github.io/uitask.html,但是,我不确定这种方法是否正确或线程安全.似乎有很多样板代码应该由 Dispatacher.RunAsync() 处理?我读过的其他文章提到了 Action 委托,我认为它应该是线程安全的,但是在写了几篇(非常详细的技术文章)之后,我现在很困惑.附加文章:C# 委托线程安全吗?(不幸的是,我只能链接到 2 篇文章 - 需要先增加我的积分!)

解决方案

我不确定这种方法是否正确或线程安全.似乎有很多样板代码应该由 Dispatacher.RunAsync() 处理?

您可以通过使用 UI 元素的 Dispatcher 属性或存在于 UI 线程上下文中的任何对象的 Dispatcher 属性安全地访问 UI 元素(例如按钮所在的页面).

在 UI 线程上创建的 Windows 运行时对象的生命周期受线程生命周期的限制.不要在窗口关闭后尝试访问 UI 线程上的对象.

以下是关于 Microsoft github 存储库.

<块引用>

通常,您可以通过调用 Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>/* update the UI */) 从后台线程更新 UI.然而,这只是在 UI 线程上安排工作并立即返回,即使任务是等待用户在弹出框中输入也是如此.它也不提供任务将结果返回给调用者的方法.

RunTaskAsync 提供了一种替代方案,它结合使用 TaskCompletionSource 和 RunAsync 来返回一个您可以从后台线程等待的任务,从而暂停执行,直到 UI 任务完成.

因为 RunTaskAsync 是一个扩展方法,你把它当作一个 Dispatcher 上的方法来调用: var result = await Dispatcher.RunTaskAsync(async () => { ...; return value; });

使用系统;使用 System.Threading.Tasks;使用 Windows.UI.Core;公共静态类 DispatcherTaskExtensions{公共静态异步任务<T>RunTaskAsync(这个 CoreDispatcher 调度程序,Func<Task<T>>func, CoreDispatcherPriority 优先级 = CoreDispatcherPriority.Normal){var taskCompletionSource = new TaskCompletionSource();await dispatcher.RunAsync(priority, async() =>{尝试{taskCompletionSource.SetResult(await func());}捕获(异常前){taskCompletionSource.SetException(ex);}});返回等待 taskCompletionSource.Task;}//没有 TaskCompletionSource所以我们使用我们扔掉的布尔值.公共静态异步任务 RunTaskAsync(这个 CoreDispatcher 调度程序,功能<任务>func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) =>await RunTaskAsync(dispatcher, async () => { await func(); return false; }, priority);}

<块引用>

是否有必要锁定多个线程通过调度程序访问同一个 uwp 组件的代码片段?

您不必添加锁.Dispatcher.RunTaskAsync 请求不会在其他代码中间运行(这就是它们的全部意义).

First, I don’t think I have titled this correctly – after much thought.

However, I have been trying to find a way to update a UI control from multiple threads spawned from a Task.Run(async ()=>{ }) expression/method. I’m doing this in an UWP windows mobile 10 application.

I've listed majority of the routines involved, and need help with completing the call to the DisplayProgress(string message) method. In this method, I want to update the UI control in a thread safe manner using Dispathcer.RunAsync(), but need it to be thread safe. Please see Attempt 1&2 in the code below.

public sealed partial class SynchProcess : BasePage
{
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        Task.Run(async ()=>{
            var result = await SynchTables();
        });
    }

    private async Task<bool> SynchTables()
    {
        Bool bRet = true;
        List<Task> tasks = new List<Task>();
        Try
        {
            // Refresh 1
            tasks.Add(Task.Run(async () => 
            {
                // Update UI
                DisplayProgress("Cars refresh…running"); 
                List<Car> cars = await _dataService.GetCarsData();
                DataAccessSQLite.DeleteAll<Car>();
                DataAccessSQLite.InsertCars(cars);
                // Update UI
                DisplayProgress("Cars refresh…completed"); 
            }));

            // Refresh 2
            tasks.Add(Task.Run(async () =>
            {
                //Update UI
                DisplayProgress("Tracks refresh…running"); 
                List<Track> tracks = await _dataService.GeTrackstData();
                DataAccessSQLite.DeleteAll<Track>();
                DataAccessSQLite.InsertTracks(tracks);
                //Update UI
                DisplayProgress("Tracks refresh…completed"); 

            }));

            // Refresh 3
            // Refresh 4
            …
            …                

            Task.WaitAll(tasks.ToArray());

        }
        Catch(AggreggateException agEx)
        {
            bRet = false;
            //  removed for brevity
            …
        }

        return bRet;
    }

    // Attempt  1 : This i was aware of, but included for completeness.
    // FAIL - The await operator can only be used in an async method.

    private static void DisplayProgress(string message)
    {
        lock(_lockObject)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                //Update control logic;
            });
    }

    // Attempt 2
    // FAIL - An object reference is required for non-static field, method or   
    //        property ‘DependencyObject.Dispatcher’

    private static async Task DisplayProgress (string message)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            // Update UI logic here
        });
    }


}

I found this article http://briandunnington.github.io/uitask.html, however, I’m not sure if this approach is correct or thread safe. Seems like a lot of boilerplate code for something that should be handled by Dispatacher.RunAsync()? Additional articles I’ve read pointed to Action delegates, which I think is supposed to be thread safe, but after several (very detailed technical articles) I am now confused. Additional articles: Are C# delegates thread-safe? (unfortunately, I can only link to 2 articles - need to increase my points first !)

解决方案

I’m not sure if this approach is correct or thread safe. Seems like a lot of boilerplate code for something that should be handled by Dispatacher.RunAsync()?

You can access the UI element safely by using its Dispatcher property, or the Dispatcher property of any object that exists in the context of the UI thread (such as the page the button is on).

The lifetime of a Windows Runtime object that is created on the UI thread is bounded by the lifetime of the thread. Do not try to access objects on a UI thread after the window has closed.

The following is official suggestion on a Microsoft github repository.

Normally, you can update your UI from a background thread by calling Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => /* update the UI */). However, this merely schedules the work on the UI thread and returns immediately, even if the task is to await user input in a popup box. It also does not provide a way for the task to return a result to the caller.

RunTaskAsync provides an alternative that uses TaskCompletionSource in combination with RunAsync to return a Task that you can await from your background thread, thereby pausing execution until the UI task completes.

Because RunTaskAsync is an extension method, you call it as if it were a method on Dispatcher: var result = await Dispatcher.RunTaskAsync(async () => { ...; return value; });

using System;
using System.Threading.Tasks;
using Windows.UI.Core;

public static class DispatcherTaskExtensions
{
    public static async Task<T> RunTaskAsync<T>(this CoreDispatcher dispatcher, 
        Func<Task<T>> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
    {
        var taskCompletionSource = new TaskCompletionSource<T>();
        await dispatcher.RunAsync(priority, async () =>
        {
            try
            {
                taskCompletionSource.SetResult(await func());
            }
            catch (Exception ex)
            {
                taskCompletionSource.SetException(ex);
            }
        });
        return await taskCompletionSource.Task;
    }

    // There is no TaskCompletionSource<void> so we use a bool that we throw away.
    public static async Task RunTaskAsync(this CoreDispatcher dispatcher,
        Func<Task> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) => 
        await RunTaskAsync(dispatcher, async () => { await func(); return false; }, priority);
}

Is it necessary to lock code snippet where multiple threads access same uwp component via dispatcher?

You don't have to add a lock. Dispatcher.RunTaskAsync requests will not run in the middle of other code (that's the whole point of them).

这篇关于如何在线程安全的方法中调用 Dispatcher.RunAsync() 来更新 UI 组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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