我正在尝试优化与Monitor.Wait和Monitor.Pulse方法类似的异步版本(在基本功能上)。这个想法是在异步方法上使用它。
要求:
1)我有一个正在运行的任务,它负责等待有人向我的显示器发出脉冲。
2)该任务可能会计算复杂(即耗时)的操作。同时,可以不做任何事情多次调用pulse方法(因为主要任务已经在进行一些处理)。
3)主任务完成后,它开始再次等待,直到另一个Pulse进入。
最坏的情况是Wait> Pulse> Wait> Pulse> Wait ...,但是通常每次等待我都会有十分之几的脉冲。
因此,我有以下类(class)(正在工作,但我认为可以根据自己的需求进行一些优化)
internal sealed class Awaiter
{
private readonly ConcurrentQueue<TaskCompletionSource<byte>> _waiting = new ConcurrentQueue<TaskCompletionSource<byte>>();
public void Pulse()
{
TaskCompletionSource<byte> tcs;
if (_waiting.TryDequeue(out tcs))
{
tcs.TrySetResult(1);
}
}
public Task Wait()
{
TaskCompletionSource<byte> tcs;
if (_waiting.TryPeek(out tcs))
{
return tcs.Task;
}
tcs = new TaskCompletionSource<byte>();
_waiting.Enqueue(tcs);
return tcs.Task;
}
}
上述类的问题是我仅用于同步的行李。因为我将只从一个线程等待,所以实际上并不需要ConcurrentQueue,因为我始终只有一个线程。
因此,我对其进行了简化,并编写了以下内容:
internal sealed class Awaiter2
{
private readonly object _mutex = new object();
private TaskCompletionSource<byte> _waiting;
public void Pulse()
{
var w = _waiting;
if (w == null)
{
return;
}
lock (_mutex)
{
w = _waiting;
if (w == null)
{
return;
}
_waiting = null;
w.TrySetResult(1);
}
}
public Task Wait()
{
var w = _waiting;
if (w != null)
{
return w.Task;
}
lock (_mutex)
{
w = _waiting;
if (w != null)
{
return w.Task;
}
w = _waiting = new TaskCompletionSource<byte>();
return w.Task;
}
}
}
该新版本也可以正常工作,但是我仍然认为可以通过删除锁来对其进行更多优化。
我正在寻找有关如何优化第二个版本的建议。有任何想法吗?
最佳答案
因为您只有一个任务在等待,所以您的功能可以简化为
internal sealed class Awaiter3
{
private volatile TaskCompletionSource<byte> _waiting;
public void Pulse()
{
var w = _waiting;
if (w == null)
{
return;
}
_waiting = null;
#if NET_46_OR_GREATER
w.TrySetResult(1);
#else
Task.Run(() => w.TrySetResult(1));
#endif
}
//This method is not thread safe and can only be called by one thread at a time.
// To make it thread safe put a lock around the null check and the assignment,
// you do not need to have a lock on Pulse, "volatile" takes care of that side.
public Task Wait()
{
if(_waiting != null)
throw new InvalidOperationException("Only one waiter is allowed to exist at a time!");
#if NET_46_OR_GREATER
_waiting = new TaskCompletionSource<byte>(TaskCreationOptions.RunContinuationsAsynchronously);
#else
_waiting = new TaskCompletionSource<byte>();
#endif
return _waiting.Task;
}
}
我确实改变了一种行为。如果使用的是.NET 4.6或更高版本,则使用
#if NET_46_OR_GREATER
块中的代码;如果使用的是else块,则使用。当您调用TrySetResult
时,您可以同步运行延续,这可能会导致Pulse()
花费很长时间才能完成。通过在.NET 4.6中使用TaskCreationOptions.RunContinuationsAsynchronously
或在4.6之前的版本中将TrySetResult
包装在Task.Run
中,将确保继续执行任务不会阻止Puse()
。有关如何制作可在您的代码中使用的
NET_46_OR_GREATER
定义,请参见SO问题Detect target framework version at compile time。关于c# - Monitor.Pulse/Wait的异步版本,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34792699/