简体   繁体   English

队列调用异步方法

[英]Queue calls to an async method

I have an async operation named Refresh. 我有一个名为Refresh的异步操作。 If a second call to refresh is made before the first is finished I need to queue it. 如果在第一次完成之前进行第二次刷新调用,我需要对其进行排队。 This is what I have: 这就是我所拥有的:

public async Task Refresh(RefreshArgs refreshArgs)
{
    await EnqueueRefreshTask(refreshArgs);
}

private Queue<RefreshArgs> refreshQueue =
    new Queue<RefreshArgs>();

private async Task EnqueueRefreshTask(RefreshArgs refreshArgs)
{
    refreshQueue.Enqueue(refreshArgs);

    await ProcessRefreshQueue();
}

private Task currentRefreshTask = null;

private async Task ProcessRefreshQueue()
{
    if ((currentRefreshTask == null) || (currentRefreshTask.IsCompleted))
    {
        if (refreshQueue.Count > 0)
        {
            var refreshArgs = refreshQueue.Dequeue();

            currentRefreshTask = DoRefresh(refreshArgs);
            await currentRefreshTask;

            await ProcessRefreshQueue();
        }           
    }
}

private async Task DoRefresh(RefreshArgs refreshArgs)
{
    // Lots of code here, including calls to a server that are executed with await.
    // Code outside my control may make another Refresh(args) call while this one is still processing.
    // I need this one to finish before processing the next.
}

It works, but I'm not sure it's the best way to do this with Tasks. 它有效,但我不确定这是使用Tasks执行此操作的最佳方法。 Any thoughts? 有什么想法吗?

Update: 更新:

I tried using ActionBlock: 我尝试使用ActionBlock:

public async Task Refresh(RefreshArgs refreshArgs)
{
    if (refreshActionBlock == null)
    {
        var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions();
        executionDataflowBlockOptions.MaxMessagesPerTask = 1;
        executionDataflowBlockOptions.TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        refreshActionBlock = new ActionBlock<RefreshArgs>(args => DoRefresh(args), executionDataflowBlockOptions);
    }

    await refreshActionBlock.SendAsync(refreshArgs);
}

This queues DoRefresh, and allows it to run in the UI thread (which I need). 这会将DoRefresh排队,并允许它在UI线程(我需要)中运行。 Problem is SendAsync doesn't await on the work of DoRefresh. 问题是SendAsync没有等待DoRefresh的工作。

SendAsync: "Asynchronously offers a message to the target message block, allowing for postponement". SendAsync:“异步向目标消息块提供消息,允许推迟”。 I'm only awaiting on the send, not the action its-self. 我只是在等待发送,而不是动作本身。

Doing this doesn't work as expected: 这样做不会按预期工作:

await Refresh(RefreshArgs.Something);
// other code goes here. It expects the first refresh to be finished.
await Refresh(RefreshArgs.SomethingElse);
// other code goes here. It expects the second refresh to be finished.

The ActionBlock will queue the second refresh, but the awaits fall through before the refresh is done. ActionBlock将排队第二次刷新,但等待刷新完成之前等待。 I need them to return when the work of DoRefresh is done. 当DoRefresh的工作完成时,我需要它们返回。

I think the simplest way to do this is to use an AsyncLock . 我认为最简单的方法是使用AsyncLock You can get one from Stephen Cleary's library AsyncEx , or you can read Stephen Toub's article about how to build it yourself . 您可以从Stephen Cleary的图书馆AsyncEx获得一个,或者您可以阅读Stephen Toub关于如何自己构建它的文章

When you have AsyncLock , implementing your Refresh() is straightforward: 有了AsyncLock ,实现Refresh()非常简单:

public async Task Refresh(RefreshArgs refreshArgs)
{
    using (await m_lock.LockAsync())
    {
        // do your async work here
    }
}

This will make sure the Refresh() es execute one after the other (and not interleaved) and also that the Task returned from Refresh() completes only after the Refresh() is actually done. 这将确保Refresh() es一个接一个地执行(而不是交错),并且从Refresh()返回的Task仅在Refresh()实际完成后才完成。

You could use ActionBlock from TPL Dataflow to do the same, but you would also need to use TaskCompletionSource and it would be much more complicated than the AsyncLock version. 可以使用TPL Dataflow中的ActionBlock来执行相同操作,但您还需要使用TaskCompletionSource ,它将比AsyncLock版本复杂得多。

This is very easy :) Only u have need to use " switch - case " !! 这很容易:)只有你需要使用“开关 - 案例”!! now Let's go to show u how to do with small sample code . 现在让我们来展示如何处理小样本代码。 sample scenario : we want to download 2 pic , one after another as a queue. 示例场景:我们希望下载2张图片,一个接一个地作为队列。

namespace Test { 命名空间Test {

public partial class SampleOfQueue : Form
{

    public SampleOfQueue() { InitializeComponent(); }
    DBContext db = new DBContext();
    int Queue;

    private void btnStartApp_Click(object sender, EventArgs e)
    {
        Queue = 0;
        DownloadQueue();
    }

    private void DownloadQueue()
    {
        switch (Queue)
        {
            case 0:
                {
                    DownloadFile("http://images.forbes.com/media/lists/53/2009/tom-cruise.jpg", "Tom Cruise 1", "");
                    Queue += 1; break;
                }
            case 1:
                {
                    DownloadFile("https://upload.wikimedia.org/wikipedia/commons/6/69/Tom_Cruise_Collateral.jpg", "Tom Cruise 2", "");
                    Queue += 1; break;
                }
            case 2:
                {
                    // Other....
                    Queue += 1; break;
                }
            default: break;
        }

    }

    public void DownloadFile(string urlAddress, string ImageName, string ImageFarsiName)
    {
        WebClient webClient = new WebClient();
        webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);

        Uri URL = urlAddress.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ? new Uri(urlAddress) : new Uri(urlAddress);
        webClient.DownloadFileAsync(URL, "d:\\x.jpg");

    }

    private void Completed(object sender, AsyncCompletedEventArgs e)
    {
        DownloadQueue();
        if (Queue == 3) { MessageBox.Show("finish"); }
    }

}

} }

Tested with C# 4.6 in VS 2015 windows form. 在VS 2015 Windows窗体中使用C#4.6进行测试。 Innovative method of myself :) I hope to be useful <3 Good luck. 我自己的创新方法:)我希望有用<3祝你好运。

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

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