在C#中将while循环与Task.Run()结合使用

时间:2015-01-24 11:28:57

标签: c# .net asynchronous task-parallel-library async-await

我对C#中的多线程应用程序很陌生,我尝试编辑下面的代码,以便它在多个线程上运行。现在它同步运行,占用很少的CPU功率。我需要它在多个线程上运行得更快。我的想法是为每个核心开始一项任务,然后当任务完成时,如果可能的话,允许另一个人取代它或类似的东西。

static void Main(string[] args)
    {
        string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
        QueueClient Client = QueueClient.CreateFromConnectionString(connectionString, "OoplesQueue");

        try
        {
            while (true)
            {
                Task.Run(() => processCalculations(Client));
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }
    }

    public static ConnectionMultiplexer connection;
    public static IDatabase cache;

    public static async Task processCalculations(QueueClient client)
    {
        try
        {
            BrokeredMessage message = await client.ReceiveAsync();

            if (message != null)
            {
                if (connection == null || !connection.IsConnected)
                {

                    connection = await ConnectionMultiplexer.ConnectAsync("connection,SyncTimeout=10000,ConnectTimeout=10000");
                    //connection = ConnectionMultiplexer.Connect("connection,SyncTimeout=10000,ConnectTimeout=10000");
                }

                cache = connection.GetDatabase();

                string sandpKey = message.Properties["sandp"].ToString();
                string dateKey = message.Properties["date"].ToString();
                string symbolclassKey = message.Properties["symbolclass"].ToString();
                string stockdataKey = message.Properties["stockdata"].ToString();
                string stockcomparedataKey = message.Properties["stockcomparedata"].ToString();

                List<StockData> sandp = cache.Get<List<StockData>>(sandpKey);
                DateTime date = cache.Get<DateTime>(dateKey);
                SymbolInfo symbolinfo = cache.Get<SymbolInfo>(symbolclassKey);
                List<StockData> stockdata = cache.Get<List<StockData>>(stockdataKey);
                List<StockMarketCompare> stockcomparedata = cache.Get<List<StockMarketCompare>>(stockcomparedataKey);

                StockRating rating = performCalculations(symbolinfo, date, sandp, stockdata, stockcomparedata);

                if (rating != null)
                {
                    saveToTable(rating);
                    if (message.LockedUntilUtc.Minute <= 1)
                    {
                        await message.RenewLockAsync();
                    }
                    await message.CompleteAsync();
                }
                else
                {
                    Console.WriteLine("Message " + message.MessageId + " Completed!");
                    await message.CompleteAsync();
                }
            }
        }
        catch (TimeoutException time)
        {
            Console.WriteLine(time.Message);
        }
        catch (MessageLockLostException locks)
        {
            Console.WriteLine(locks.Message);
        }
        catch (RedisConnectionException redis)
        {
            Console.WriteLine("Start the redis server service!");
        }
        catch (MessagingCommunicationException communication)
        {
            Console.WriteLine(communication.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }
    }

3 个答案:

答案 0 :(得分:4)

这看起来像是一种典型的生产者 - 消费者模式。

在这种情况下,您需要将并发与 async IO 绑定操作(例如从Redis缓存中检索数据)和 CPU绑定操作(例如执行计算)相结合约束计算),我将TPL Dataflow用于工作。

您可以使用负责处理您传递给它的单个操作的ActionBlock<T>。在幕后,它负责并发,而你可以通过传递ExecutionDataflowBlockOptions来限制它。

首先创建ActionBlock<BrokeredMessage>

private static void Main(string[] args)
{
    var actionBlock = new ActionBlock<BrokeredMessage>(async message =>
                      await ProcessCalculationsAsync(message),
                      new ExecutionDataflowBlockOptions 
                      {
                          MaxDegreeOfParallelism = Environment.ProcessorCount
                      });

    var produceMessagesTask = Task.Run(async () => await
                                                   ProduceBrokeredMessagesAsync(client, 
                                                   actionBlock));

    produceMessagesTask.Wait();
}

现在让我们看看ProduceBrokeredMessageAsync。它只会收到您的QueueClientActionBlock以下内容:

private async Task ProduceBrokeredMessagesAsync(QueueClient client,
                                                ActionBlock<BrokeredMessage> actionBlock)
{
    BrokeredMessage message;
    while ((message = await client.ReceiveAsync()) != null)
    {
        await actionBlock.SendAsync(message);
    }
    actionBlock.Complete();
    await actionBlock.Completion;
}

当您收到来自QueueClient的消息时,它会将消息异步发布到ActionBlock,后者将同时处理这些消息。

答案 1 :(得分:3)

  

现在它同步运行,占用很少的CPU功率。我需要它在多个线程上运行得更快。

&#34;多线程&#34;并不一定意味着&#34;更快&#34;。只有当您执行多个彼此独立的计算时才会出现这种情况,并且它们受CPU限制(这意味着它们主要涉及CPU操作,而不是IO操作)。

此外,异步并不一定意味着多个线程。它只是意味着您的操作不会在进行中阻止进程线程。如果您正在启动另一个线程并阻止它,那么看起来像异步,但它确实不是。看看第9频道视频:Async Library Methods Shouldn't Lie

processCalculations中的大部分操作看起来都是相互依赖的;但是,这部分可能是一个潜在的改进点:

List<StockData> sandp = cache.Get<List<StockData>>(sandpKey);
DateTime date = cache.Get<DateTime>(dateKey);
SymbolInfo symbolinfo = cache.Get<SymbolInfo>(symbolclassKey);
List<StockData> stockdata = cache.Get<List<StockData>>(stockdataKey);
List<StockMarketCompare> stockcomparedata = cache.Get<List<StockMarketCompare>>(stockcomparedataKey);
StockRating rating = performCalculations(symbolinfo, date, sandp, stockdata, stockcomparedata);

我不熟悉您正在使用的API,但如果它包含Get方法的异步等效项,您可以并行异步执行这些IO操作,例如:< / p>

var sandpTask = List<StockData> sandp = cache.GetAsync<List<StockData>>(sandpKey);
var dateTask = cache.GetAsync<DateTime>(dateKey);
var symbolinfoTask = cache.GetAsync<SymbolInfo>(symbolclassKey);
var stockdataTask = cache.GetAsync<List<StockData>>(stockdataKey);
var stockcomparedataTask = cache.GetAsync<List<StockMarketCompare>>(stockcomparedataKey);

await Task.WhenAll(sandpTask, dateTask,symbolinfoTask,
    stockdataTask, stockcomparedataTask);

List<StockData> sandp = sandpTask.Result;
DateTime date = dateTask.Result;
SymbolInfo symbolinfo = symbolinfoTask.Result;
List<StockData> stockdata = stockdataTask.Result;
List<StockMarketCompare> stockcomparedata = stockcomparedataTask.Result;

StockRating rating = performCalculations(symbolinfo, date, sandp, stockdata, stockcomparedata);

另外,请注意,您不需要将processCalculations调用包装在另一个Task中,因为它已经返回了一个任务:

// instead of Task.Run(() => processCalculations(message));
processCalculations(message); 

答案 2 :(得分:1)

您需要两个部分:

第1部分等待传入消息:ConnectAsync()这在一个简单的循环中运行。每当收到某些内容时,就会启动Part2的实例来处理传入的消息。

Part2在另一个线程/后台运行并处理单个传入消息。

这样,Part2的几个实例可以并行运行。

所以你的结构是这样的:

while (true)
{
    connection = await ConnectionMultiplexer.ConnectAsync(...);
    StartProcessCalculationsInBackground(connection, ...); // return immediately
}