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

C# 如何同步UI更新的多线程通知,c#,.net,synchronization,async-await,task-parallel-library,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.W

我有一个异步方法,当我需要更新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);
}
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);
    }
}
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
当服务器上的数据发生更改时,我会收到来自不同线程的通知,我必须在UI线程上调用UpdateUI方法,如下所示:

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);
}
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);
    }
}
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
我模拟多线程通知:

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

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
如何同步以使方法按如下顺序执行:

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);
}
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);
    }
}
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
前进

编辑: 我找到了解决办法:

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);
    }
}

您的问题在于
Task.Factory.StartNew
不能正确地“处理”返回lambda的
异步任务,因为它们生成了
任务
,而实际上是
等待外部
任务
,而不是内部任务

您可以使用
信号量lim
代替
信号量
,它有一个可以异步等待的

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

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
,这是针对事件处理程序的。请改为执行异步任务。

您可以在不同的功能阶段检查
系统.Threading.SynchronizationContext
吗?要找出与WPF/WF dispatcher的同步在哪里丢失?@Nitram,应始终在UI线程上执行UpdateUI。由于UpdateUI方法内的GetDataFromServerAsync方法调用,同步丢失,然后释放信号量并重新进入UpdateUI。如何防止在执行UpdateUI方法结束之前释放信号量?这很奇怪<代码>等待
应始终捕获并恢复同步上下文。我找到了解决方案!需要在OnServerDataChanged中使用TaskCompletionSource!O.O什么?你能回答你自己的问题来说明你是如何解决的吗?我想我不明白你是如何使用
TaskCompletionSource
的。