简体   繁体   English

使用 async/await 快速处理队列

[英]Processing a queue quickly with async/await

I often have scenarios where I want to queue data to be processed, and then process it in the background so that the main program can continue.我经常有这样的场景,我想将要处理的数据排队,然后在后台处理,以便主程序可以继续。 I've written the following class to try and help with this:我编写了以下课程来尝试帮助解决这个问题:

public class ProcessQueue<T>
{
    #region Properties

    public Task ProcessingComplete => tcs.Task;

    #endregion

    #region Variables

    private ConcurrentQueue<T> queue = new ConcurrentQueue<T>();
    private bool processingQueue = false;
    private bool stopped = false;

    private Func<T, Task> processItemFunc;
    private TaskCompletionSource tcs = new TaskCompletionSource();

    #endregion

    public ProcessQueue(Func<T, Task> processItemFunc)
    {
        this.processItemFunc = processItemFunc;
    }

    public void EnqueueAndProcess(T data)
    {
        if (stopped)
        {
            return;
        }

        queue.Enqueue(data);

        ProcessQueuedItems();
    }

    public void Stop()
    {
        stopped = true;

        queue.Clear();
        tcs.SetResult();
    }

    private void ProcessQueuedItems()
    {
        if (processingQueue)
        {
            return;
        }

        processingQueue = true;
        tcs = new TaskCompletionSource();

        new TaskFactory().StartNew(async () =>
        {
            while (queue.Count > 0)
            {
                if (queue.TryDequeue(out var item))
                {
                    await processItemFunc(item);
                }
            }

            processingQueue = false;
            tcs.SetResult();
        }, TaskCreationOptions.LongRunning);
    }
}

It seems fairly straightforward so I wrote some unit tests for it, such as the following:这看起来相当简单,所以我为它编写了一些单元测试,例如:

[TestClass]
public class ProcessQueueTests
{
    private ProcessQueue<int> sut;
    private List<int> processedOutput = new();

    [TestInitialize]
    public void Initialise()
    {
        sut = new ProcessQueue<int>(ProcessAsync);
    }

    [TestMethod]
    public async Task Test1()
    {
        for (int i = 0; i < 1000; i++)
        {
            sut.EnqueueAndProcess(i);
        }

        await sut.ProcessingComplete;

        Assert.AreEqual(1000, processedOutput.Count);

        for (int i = 0; i < processedOutput.Count; i++)
        {
            Assert.AreEqual(i, processedOutput[i]);
        }
    }

    private async Task ProcessAsync(int value)
    {
        await Task.Delay(1);

        processedOutput.Add(value);
    }
}

I've put a delay in there to see how that affects it, and indeed for the above test it is constantly taking nearly 16 seconds to complete, when I would have hoped for slightly over 1 second.我在那里放了一个延迟,看看它是如何影响它的,实际上对于上面的测试,它总是需要将近 16 秒才能完成,而我本来希望这会稍微超过 1 秒。

Why is this taking so long?为什么这需要这么长时间? How can it be faster?怎么可能更快?

Task.Delay is just pretty low resolution. Task.Delay分辨率很低。 As documentation states:正如文档所述:

This method depends on the system clock.此方法取决于系统时钟。 This means that the time delay will approximately equal the resolution of the system clock if the millisecondsDelay argument is less than the resolution of the system clock, which is approximately 15 milliseconds on Windows systems.这意味着如果毫秒延迟参数小于系统时钟的分辨率(在 Windows 系统上约为 15 毫秒),则时间延迟将大约等于系统时钟的分辨率。

Your argument ( 1 ) is less than 15 milliseconds, so it gets adjusted to that.您的参数 ( 1 ) 小于 15 毫秒,因此会对此进行调整。 You can just do:你可以这样做:

var watch = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++) {
    await Task.Delay(1);
}
watch.Stop();
Console.WriteLine($"Took {watch.ElapsedMilliseconds}ms");

to reproduce that.重现那个。 1000 * 15ms = 15 seconds, which is about what you report. 1000 * 15ms = 15 秒,这与您报告的内容有关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM