我对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);
}
}
答案 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
。它只会收到您的QueueClient
和ActionBlock
以下内容:
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
}