我正在尝试优化与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/

10-13 08:07