如何同步UI更新的多线程通知

时间:2015-06-25 11:56:38

标签: c# .net synchronization async-await task-parallel-library

当我需要更新UI控件内容时,我有一个异步方法,如下所示:

public async Task UpdateUI(int i)
{
    Debug.WriteLine("Enter {0}", i);
    DoSomethingSync(1000);

    Debug.WriteLine("Await {0}", i);
    await GetDataFromServerAsync(5000);

    //Update UI Controls
    Debug.WriteLine("Update {0}", i);
    DoSomethingSync(2000);

    Debug.WriteLine("Exit {0}", i);
}

public void DoSomethingSync(int delay)
{
    Thread.Sleep(delay);
}

public Task GetDataFromServerAsync(int delay)
{
    return TaskEx.Delay(delay);
}

当在服务器上更改数据时,我收到来自不同线程的通知,我必须在UI线程上调用UpdateUI方法,如下所示:

public async void OnServerDataChanged(int i)
{
    Debug.WriteLine("WaitOne {0}", i);
    _semaphore.WaitOne();
    try
    {
        await Task.Factory.StartNew(async () =>
        {
            await UpdateUI(i);
        }, CancellationToken.None, TaskCreationOptions.None, _scheduler);
    }
    finally 
    {
        _semaphore.Release();
        Debug.WriteLine("Release {0}", i);
    }
}

我模拟多线程通知:

public void SimulateMultiThreadingNotification()
{
    TaskEx.Run(() =>
    {
        for (var i = 0; i < 3; i++)
        {
            TaskEx.Run(() =>
            {
                var id = Thread.CurrentThread.ManagedThreadId;
                OnServerDataChanged(id);
            });
        }
    });
}

执行命令

_semaphore = new Semaphore(1, 1);
_scheduler = TaskScheduler.FromCurrentSynchronizationContext();
SimulateMultiThreadingNotification();

输出:

Enter 11
Await 11
Release 11
Enter 12
Await 12
Release 12
Enter 6
Await 6
Release 6
Update 11
Exit 11
Update 12
Exit 12
Update 6
Exit 6

如何同步以便方法按顺序执行:

Enter 11
Await 11
Update 11
Exit 11
Release 11
Enter 12
Await 12
Update 12
Exit 12
Release 12
Enter 6
Await 6
Update 6
Exit 6
Release 6

推进thanx!

编辑: 我找到了解决方案:

public void OnServerDataChanged(int i)
{
    _semaphore.WaitOne();
    try
    {
        var tcs = new TaskCompletionSource<bool>();
        Task.Factory.StartNew(async () =>
        {
            await UpdateUI(i);
            tcs.SetResult(true);
        }, CancellationToken.None, TaskCreationOptions.None, _scheduler);
        tcs.Task.Wait();
    }
    finally 
    {
        _semaphore.Release();
        Debug.WriteLine("Release {0}", i);
    }
}

编辑2:Yuval Itzchakov的解决方案:

public void OnServerDataChanged(int i)
{
    _semaphore.WaitOne();
    try
    {
        Task.Factory.StartNew(async () =>
        {
            await UpdateUI(i);
        }, CancellationToken.None, TaskCreationOptions.None, _scheduler).Unwrap().Wait();
    }
    finally 
    {
        _semaphore.Release();
        Debug.WriteLine("Release {0}", i);
    }
}

1 个答案:

答案 0 :(得分:1)

Your problem is with the fact Task.Factory.StartNew doesn't "handle" async Task returning lambdas properly, since they generate a Task<Task>, where you actually await on the outter Task, not the inner one. You can use SemaphoreSlim instead of a Semaphore, it has a WaitAsync which you can asynchronously wait on. Calling Unwrap() will solve your problem: private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); public async Task ServerDataChangedAsync(int i) { Debug.WriteLine("WaitAsync {0}", i); await _semaphore.WaitAsync(); try { await Task.Factory.StartNew(async () => { await UpdateUI(i); }, CancellationToken.None, TaskCreationOptions.None, _scheduler).Unwrap(); } finally { _semaphore.Release(); Debug.WriteLine("Release {0}", i); } } Also, don't do async void, that's for event handlers. Do async Task instead.