gpt4 book ai didi

c# - 如何避免使用 async void 事件处理程序重入?

转载 作者:可可西里 更新时间:2023-11-01 08:24:02 32 4
gpt4 key购买 nike

在 WPF 应用程序中,我有一个通过网络接收消息的类。每当所述类的对象收到一条完整的消息时,就会引发一个事件。在应用程序的主窗口中,我有一个事件处理程序订阅了该事件。保证在应用程序的 GUI 线程上调用事件处理程序。

无论何时调用事件处理程序,都需要将消息的内容应用于模型。这样做可能会非常昂贵(在当前硬件上 >200ms)。这就是使用 Task.Run 将应用消息卸载到线程池的原因。

现在,可以非常连续地接收消息,因此可以在仍在处理之前的更改时调用事件处理程序。确保一次只应用一条消息的最简单方法是什么?到目前为止,我已经得出以下结论:

using System;
using System.Threading.Tasks;
using System.Windows;

public partial class MainWindow : Window
{
private Model model = new Model();
private Task pending = Task.FromResult<bool>(false);

// Assume e carries a message received over the network.
private void OnMessageReceived(object sender, EventArgs e)
{
this.pending = ApplyToModel(e);
}

private async Task ApplyToModel(EventArgs e)
{
await this.pending;
await Task.Run(() => this.model.Apply(e)); // Assume this is an expensive call.
}
}

这似乎按预期工作,但也似乎这将不可避免地产生“内存泄漏”,因为应用消息的任务将始终首先等待应用前一条消息的任务。如果是这样,那么以下更改应该可以避免泄漏:

private async Task ApplyToModel(EventArgs e)
{
if (!this.pending.IsCompleted)
{
await this.pending;
}

await Task.Run(() => this.model.Apply(e));
}

这是避免使用 async void 事件处理程序重入的明智方法吗?

编辑:删除了 OnMessageReceived 中不必要的 await this.pending; 语句。

编辑 2:消息必须以与收到消息相同的顺序应用于模型。

最佳答案

我们需要在这里感谢 Stephen Toub,因为他在博客系列中展示了一些非常有用的异步锁定结构,包括 async lock block 。

这是那篇文章的代码(包括本系列上一篇文章的一些代码):

public class AsyncLock
{
private readonly AsyncSemaphore m_semaphore;
private readonly Task<Releaser> m_releaser;

public AsyncLock()
{
m_semaphore = new AsyncSemaphore(1);
m_releaser = Task.FromResult(new Releaser(this));
}

public Task<Releaser> LockAsync()
{
var wait = m_semaphore.WaitAsync();
return wait.IsCompleted ?
m_releaser :
wait.ContinueWith((_, state) => new Releaser((AsyncLock)state),
this, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

public struct Releaser : IDisposable
{
private readonly AsyncLock m_toRelease;

internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; }

public void Dispose()
{
if (m_toRelease != null)
m_toRelease.m_semaphore.Release();
}
}
}

public class AsyncSemaphore
{
private readonly static Task s_completed = Task.FromResult(true);
private readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>();
private int m_currentCount;

public AsyncSemaphore(int initialCount)
{
if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount");
m_currentCount = initialCount;
}
public Task WaitAsync()
{
lock (m_waiters)
{
if (m_currentCount > 0)
{
--m_currentCount;
return s_completed;
}
else
{
var waiter = new TaskCompletionSource<bool>();
m_waiters.Enqueue(waiter);
return waiter.Task;
}
}
}
public void Release()
{
TaskCompletionSource<bool> toRelease = null;
lock (m_waiters)
{
if (m_waiters.Count > 0)
toRelease = m_waiters.Dequeue();
else
++m_currentCount;
}
if (toRelease != null)
toRelease.SetResult(true);
}
}

现在将其应用到您的案例中:

private readonly AsyncLock m_lock = new AsyncLock();

private async void OnMessageReceived(object sender, EventArgs e)
{
using(var releaser = await m_lock.LockAsync())
{
await Task.Run(() => this.model.Apply(e));
}
}

关于c# - 如何避免使用 async void 事件处理程序重入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14464391/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com