使用 async/await 时如何避免 UI 线程上的竞争条件

How to to avoid race conditions on the UI thread when using async/await

我们都听说保持 UI 线程响应很重要,因此我们在任何地方实施 async/await。 我正在构建一个文本编辑器,其中 'everything' 是异步的。但是,现在我发现当其他代码完成之前的代码 运行s 时,它会在 UI 线程上受到竞争条件的影响。 当然,这就是 'responsive UI thread' 的全部想法,它可以 运行 编码,同时等待其他代码。 我需要一些代码来等待其他代码在 运行s 之前完成。我已将问题归结为这段代码,我只处理击键:

    private async void Form1_KeyPress(object sender, KeyPressEventArgs e)
    {
        //Wait until it's your turn (await HandleKey has returned) before proceeding
        await HandleKey(e.KeyChar);
    }

    async Task HandleKey(char ch)
    {
        await GetCaretPosition();
        Point newPosition = await Task.Delay(1000);
        await SetCaretPosition(newPosition);
    }

如您所见,当第一个键被处理(等待)时,下一个键可以开始处理。在这个简单的例子中,第二个键处理代码将得到一个旧的 caretposition 值, 因为第一个键处理尚未更新 caretposition。 如何让 KeyPress 事件中的代码等到第一个键完成处理? 回到同步编码不是一种选择。

首先,听起来这个问题还有很多内容,或者可能会有后续问题。

也就是说,您可以尝试让您的HandleKey同步来管理共享资源:

[MethodImpl(MethodImplOptions.Synchronized)]
async Task HandleKey(char ch)
{

试试这个话题以供参考: C# version of java's synchronized keyword?

PS - 所以我越想这个就越像是你试图同步执行一些工作单元但将它从 ui 线程卸载。也就是说,同步 HandleKey 方法不会达到预期的结果。我认为您可能正在寻找 Dispatcher 模式:

https://www.what-could-possibly-go-wrong.com/the-dispatcher-pattern/

https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher(v=vs.110).aspx

对于浏览此内容的任何人:这是我根据 k1dev 的 link 关于 SemaphoreSlim 想出的(阅读 here

实际上非常简单。根据问题中的代码,我添加了一个 SemaphoreSlim 并等待它(异步)。 SemaphoreSlim 是一个轻量级的 Semaphore,专为等待 UI 线程而设计。我使用 Queue<char> 来确保按正确的顺序处理键:

SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
Queue<char> queue = new Queue<char>();

private async void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
    queue.EnQueue(e.KeyChar);
    await semaphore.WaitAsync();
    try
    {
       await HandleKey(queue.DeQueue());
    }
    finally
    {
        semaphore.Release();
    }
}

作为奖励:我有一个方法,如果应用程序很忙,我只想跳过它,可以使用以下代码完成:

if (await semaphore.WaitAsync(0)) //Note the zero as timeout
{
    try
    {
       await MyMethod();
    }
    finally
    {
        semaphore.Release();
    }
}