首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何同步UI更新的多线程通知

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

Stack Overflow用户
提问于 2015-06-25 19:56:38
回答 1查看 141关注 0票数 1

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

代码语言:javascript
复制
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方法,如下所示:

代码语言:javascript
复制
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);
    }
}

我模拟多线程通知:

代码语言:javascript
复制
public void SimulateMultiThreadingNotification()
{
    TaskEx.Run(() =>
    {
        for (var i = 0; i < 3; i++)
        {
            TaskEx.Run(() =>
            {
                var id = Thread.CurrentThread.ManagedThreadId;
                OnServerDataChanged(id);
            });
        }
    });
}

运行:

代码语言:javascript
复制
_semaphore = new Semaphore(1, 1);
_scheduler = TaskScheduler.FromCurrentSynchronizationContext();
SimulateMultiThreadingNotification();

输出:

代码语言:javascript
复制
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

如何同步以使方法按如下方式顺序执行:

代码语言:javascript
复制
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

比x前进!

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

代码语言:javascript
复制
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的解决方案:

代码语言:javascript
复制
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);
    }
}
EN

回答 1

Stack Overflow用户

发布于 2015-06-26 02:50:49

您的问题在于Task.Factory.StartNew没有正确地“处理”返回lambda的async Task,因为它们会生成一个Task<Task>,而实际上是在外部await上执行,而不是在内部Task上执行。

你可以用SemaphoreSlim代替Semaphore,它有一个你可以异步等待的WaitAsync

调用Unwrap()可以解决您的问题:

代码语言:javascript
复制
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);
    }
}

另外,不要使用async void,这是针对事件处理程序的。改为使用async Task

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31049705

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档