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

4

我们都知道保持UI线程的响应很重要,因此我们在各个地方实现了async/await。我正在构建一个文本编辑器,其中“所有内容”都是异步的。然而,现在我发现当代码运行之前,其他代码还没有完成时,它会受到UI线程竞争条件的影响。当然,这就是“响应式UI线程”的整个想法,即它可以在等待其他代码的同时运行代码。我需要一些代码来等待其他代码完成后再运行。我已经将问题简化为以下代码,其中我只是处理按键:

    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事件中的代码等待第一个按键完成处理? 回到同步编码不是一个选项。


1
看一下这个:https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/ - k1dev
2
这就是async/await的肮脏小秘密,它带回了Application.DoEvents()产生的所有可重入错误的恶魔。虽然示例过于理论化,无法提出实际解决方案,但队列可以解决这个问题。当用户输入速度很快时,你也必须采取一些措施。我敢打赌,你的下一个项目会有所不同 :) - Hans Passant
@k1dev,我希望我能给你超过1分,因为那就是答案! - Poul Bak
2个回答

0

对于任何浏览此内容的人:这是我根据k1dev关于SemaphoreSlim的链接(在这里阅读)得出的结论。

实际上非常简单。基于问题中的代码,我添加了一个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();
    }
}

-1

首先,这个问题似乎还有更多的内容或者可能会有后续问题。

话虽如此,您可以尝试将HandleKey同步以管理共享资源:

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

可以参考这个线程: C# version of java's synchronized keyword?

PS - 所以我越想,似乎你正在尝试同步执行某个工作单元,但将其卸载到 UI 线程之外。 话虽如此,同步 HandleKey 方法并不能实现所需的结果。 我认为你可能正在寻找一个调度程序模式:

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


这会确保它们按正确顺序处理吗(所以'abc'不会变成'acb')? - Poul Bak
1
@PoulBak,请查看更新的答案。Await块会按照消息队列中出现的顺序处理事件,因此从技术上讲,它确实会按顺序处理事件。但是,我怀疑您想要实现的是在UI线程之外同步执行(即,您只想将事件分派到工作线程)。请查看调度程序模式。 - Alexander Toptygin
2
[CS4015] 'MethodImplOptions.Synchronized' 无法应用于异步方法。 - activout.se

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接