0

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 !)

smillerkyd
  • 35
  • 4

2 Answers2

3

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).

Community
  • 1
  • 1
Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
0

This solved the issue:

private async Task  DisplayProgess(string message)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {                
        lsvInvSynchProgress.Items.Add($"{message} - {DateTime.Now}");
     });
}

The revised code

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
                await DisplayProgress(“Cars refresh…running”); 
                List<Car> cars = await _dataService.GetCarsData();
                DataAccessSQLite.DeleteAll<Car>();
                DataAccessSQLite.InsertCars(cars);
                // Update UI
                await DisplayProgress(“Cars refresh…completed”); 
            }));

            // Refresh 2
            // Refresh 3
            // Refresh 4
            …
            …                

            Task.WaitAll(tasks.ToArray());

        }
        Catch(AggreggateException agEx)
        {
            bRet = false;
        }

        return bRet;
    }

    private async Task  DisplayProgess(string message)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {                
            lsvInvSynchProgress.Items.Add($"{message} - {DateTime.Now}");
        });
    }

}
smillerkyd
  • 35
  • 4