让任务竞赛

时间:2014-04-22 14:00:23

标签: c#-4.0 task-parallel-library async-await c#-5.0

假设我有一个BlockingCollection OutputQueue,它有很多项。目前我的代码是:

    public void Consumer()
    {
        foreach (var workItem in OutputQueue.GetConsumingEnumerable())
        {
            PlayMessage(workItem);
            Console.WriteLine("Works on {0}", workItem.TaskID);
            OutLog.Write("Works on {0}", workItem.TaskID);
            Thread.Sleep(500);
        }
    }

现在我希望PlayMessage(workItem)以多任务方式运行,因为有些workItem需要更多时间,其他需要更少时间。有很大的不同。

对于方法PlayMessage(workItem),它有一些服务调用,播放文本到语音和一些日志记录。

 bool successRouting = serviceCollection.SvcCall_GetRoutingData(string[] params, out ex);
 bool successDialingService = serviceCollection.SvcCall_GetDialingServiceData(string[] params, out excep);
 PlayTTS(workItem.TaskType); //  playing text to speech

那么如何更改我的代码?

我的想法是:

public async Task Consumer()
{
    foreach (var workItem in OutputQueue.GetConsumingEnumerable())
    {
        await PlayMessage(workItem);
        Console.WriteLine("Works on {0}", workItem.TaskID);
        OutLog.Write("Works on {0}", workItem.TaskID);
        Thread.Sleep(500);
    }
}

2 个答案:

答案 0 :(得分:2)

由于您希望与PlayMessage保持并行,我建议您查看TPL Dataflow,因为它将并行工作与异步相结合,因此您可以正常等待您的工作。

TPL Dataflow由Blocks构成,每个块都有自己的特性。 一些流行的是:

ActionBlock<TInput>
TransformBlock<T, TResult>

我会构建如下内容:

var workItemBlock = new ActionBlock<WorkItem>(
    workItem =>
    {
        PlayMessage(workItem);
        Console.WriteLine("Works on {0}", workItem.TaskID);
        OutLog.Write("Works on {0}", workItem.TaskID);
    }, new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = // Set max parallelism as you wish..
    });

foreach (var workItem in OutputQueue.GetConsumingEnumerable())
{
    workItemBlock.Post(workItem);
}

workItemBlock.Complete();

答案 1 :(得分:1)

这是另一种解决方案,不是基于TPL Dataflow。它使用用SemaphoreSlim来限制并行播放的数量(警告,未经测试):

public async Task Consumer()
{
    var semaphore = new SemaphoreSlim(NUMBER_OF_PORTS);
    var pendingTasks = new HashSet<Task>();
    var syncLock = new Object();

    Action<Task> queueTaskAsync = async(task) =>
    {
        // be careful with exceptions inside "async void" methods

        // keep failed/cancelled tasks in the list
        // they will be observed outside
        lock (syncLock)
            pendingTasks.Add(task);

        await semaphore.WaitAsync().ConfigureAwait(false);
        try
        {
            await task;
        }
        catch
        {
            if (!task.IsCancelled && !task.IsFaulted)
                throw;
            // the error will be observed later, 
            // keep the task in the list
            return;
        }
        finally
        {
            semaphore.Release();
        }

        // remove successfully completed task from the list
        lock (syncLock)
            pendingTasks.Remove(task);
    };

    foreach (var workItem in OutputQueue.GetConsumingEnumerable())
    {
        var item = workItem;
        Func<Task> workAsync = async () =>
        {
            await PlayMessage(item);
            Console.WriteLine("Works on {0}", item.TaskID);
            OutLog.Write("Works on {0}", item.TaskID);
            Thread.Sleep(500);
        });

        var task = workAsync();
        queueTaskAsync(task);
    }

    await Task.WhenAll(pendingTasks.ToArray());
}