Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/288.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何正确地将任务排队以在C中运行#_C#_.net_Multithreading_Asynchronous_Async Await - Fatal编程技术网

C# 如何正确地将任务排队以在C中运行#

C# 如何正确地将任务排队以在C中运行#,c#,.net,multithreading,asynchronous,async-await,C#,.net,Multithreading,Asynchronous,Async Await,我有一个项目枚举(RunData.Demand),每个项目都表示一些涉及通过HTTP调用API的工作。如果我在每次迭代中都调用API,那么它的工作效果会非常好。但是,每次迭代都需要一两秒钟的时间,因此我希望运行2-3个线程,并在它们之间分配工作。以下是我正在做的: ThreadPool.SetMaxThreads(2, 5); // Trying to limit the amount of threads var tasks = RunData.Demand .Select(servic

我有一个项目枚举(
RunData.Demand
),每个项目都表示一些涉及通过HTTP调用API的工作。如果我在每次迭代中都调用API,那么它的工作效果会非常好。但是,每次迭代都需要一两秒钟的时间,因此我希望运行2-3个线程,并在它们之间分配工作。以下是我正在做的:

ThreadPool.SetMaxThreads(2, 5); // Trying to limit the amount of threads
var tasks = RunData.Demand
   .Select(service => Task.Run(async delegate
   {
      var availabilityResponse = await client.QueryAvailability(service);
      // Do some other stuff, not really important
   }));

await Task.WhenAll(tasks);
client.QueryAvailability
调用基本上使用
HttpClient
类调用API:

public async Task<QueryAvailabilityResponse> QueryAvailability(QueryAvailabilityMultidayRequest request)
{
   var response = await client.PostAsJsonAsync("api/queryavailabilitymultiday", request);

   if (response.IsSuccessStatusCode)
   {
      return await response.Content.ReadAsAsync<QueryAvailabilityResponse>();
   }

   throw new HttpException((int) response.StatusCode, response.ReasonPhrase);
}
公共异步任务QueryAvailability(QueryAvailabilityMultidayRequest请求) { var response=wait client.postsjsonasync(“api/queryavailabilitymultiday”,请求); if(响应。IsSuccessStatusCode) { return wait response.Content.ReadAsAsync(); } 抛出新的HttpException((int)response.StatusCode,response.reasonPhase); } 这在一段时间内效果很好,但最终事情开始超时。如果我将HttpClient超时设置为一个小时,那么我开始出现奇怪的内部服务器错误

我开始做的是在
QueryAvailability
方法中设置秒表,以查看发生了什么

正在发生的是RunData中的所有1200个项目。立即创建需求,所有1200个
等待客户端。正在调用PostAsJsonAsync
方法。看起来它然后使用2个线程来缓慢地检查任务,所以在接近结束时,我有一些任务已经等待了9或10分钟

以下是我想要的行为:

我想创建1200个任务,然后在线程可用时一次运行3-4个。我不想立即将1200个HTTP调用排队


有什么好办法可以做到这一点吗?

正如我一直建议的那样。。您需要的是TPL数据流(要安装:
install Package System.Threading.Tasks.Dataflow

创建一个
ActionBlock
,其中包含对每个项目执行的操作。为节流设置
MaxDegreeOfParallelism
。开始向其发布并等待其完成:

var block = new ActionBlock<QueryAvailabilityMultidayRequest>(async service => 
{
    var availabilityResponse = await client.QueryAvailability(service);
    // ...
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });

foreach (var service in RunData.Demand)
{
    block.Post(service);
}

block.Complete();
await block.Completion;
var block=newactionblock(异步服务=>
{
var availabilityResponse=wait client.QueryAvailability(服务);
// ...
},
新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=4});
foreach(RunData.Demand中的var服务)
{
街区邮政(服务);
}
block.Complete();
等待区块完成;

您使用的是异步HTTP调用,因此限制线程数量不会有帮助(答案之一是
并行中的
并行选项.MaxDegreeOfParallelism
。ForEach
)。即使是单个线程也可以启动所有请求并在它们到达时处理结果

解决这个问题的一种方法是使用TPL数据流

另一个很好的解决方案是将源
IEnumerable
划分为多个分区,并按如下所述顺序处理每个分区中的项:

公共静态任务ForEachAsync(此IEnumerable源代码,int-dop,Func-body)
{
返回任务.WhenAll(
从Partitioner.Create(source).GetPartitions(dop)中的分区
选择Task.Run(异步委托
{
使用(分区)
while(partition.MoveNext())
等待体(partition.Current);
}));
}

虽然数据流库很棒,但我认为如果不使用块组合,它会有点沉重。我倾向于使用下面的扩展方法

此外,与Partitioner方法不同,它在调用上下文上运行异步方法-警告是,如果代码不是真正异步的,或者采用“快速路径”,那么它将有效地同步运行,因为没有显式创建线程

public static async Task RunParallelAsync<T>(this IEnumerable<T> items, Func<T, Task> asyncAction, int maxParallel)
{
    var tasks = new List<Task>();

    foreach (var item in items)
    {
        tasks.Add(asyncAction(item));

        if (tasks.Count < maxParallel)
                continue; 

        var notCompleted = tasks.Where(t => !t.IsCompleted).ToList();

        if (notCompleted.Count >= maxParallel)
            await Task.WhenAny(notCompleted);
    }

    await Task.WhenAll(tasks);
}
公共静态异步任务runparallelsync(此IEnumerable items,Func asyncAction,int maxParallel)
{
var tasks=新列表();
foreach(项目中的var项目)
{
添加(异步操作(项));
if(tasks.Count!t.IsCompleted).ToList();
如果(notCompleted.Count>=maxParallel)
等待任务。何时(未完成);
}
等待任务。何时(任务);
}

这是一个老问题,但我想提出一个使用该类的替代轻量级解决方案。只需参考System.Threading

SemaphoreSlim sem = new SemaphoreSlim(4,4);

foreach (var service in RunData.Demand)
{

    await sem.WaitAsync();
    Task t = Task.Run(async () => 
    {
        var availabilityResponse = await client.QueryAvailability(serviceCopy));    
        // do your other stuff here with the result of QueryAvailability
    }
    t.ContinueWith(sem.Release());
}

信号量充当锁定机制。您只能通过调用Wait(WaitAsync)来输入信号量,Wait(WaitAsync)从计数中减去一。调用release将向计数中添加一个

这看起来也很有希望,并且看起来它与
async
方法配合得很好(例如,我可以
wait
on block completion)。。我要试一试。@MikeChristensen是的。它是.Net中为数不多的专门为异步等待而编写的库之一。工作完美无瑕!我现在是个粉丝了。接受。您似乎没有为每个呼叫创建新的
客户端。您知道
System.Net.Http.HttpClient
对于实例调用不是线程安全的吗?应为每个调用创建一个新实例(并在每次调用后处理)。
QueryAvailability
方法实际上位于创建
HttpClient
的类中,该类是该实例的私有成员。我不知道它不是线程安全的,但我肯定可以在每次调用之前创建它。我会进一步调查的,谢谢!嗯,我做了一些研究,看起来我所做的是线程安全的。请参阅,如果我理解正确,根据使用方法的不同,可以使用
sem.Wait()
而不是
Wait-sem.WaitASync()
。这样做会阻止调用线程,因此不应该在UI线程上执行,但在任何其他线程上,这可能是管理要完成的工作的最简单方法。具体而言,如果4个SEM中有一个可用,它将立即进行。否则,它将等待一个可用的列表。在foreach循环的每次迭代中创建不必要的列表。事件或事件
SemaphoreSlim sem = new SemaphoreSlim(4,4);

foreach (var service in RunData.Demand)
{

    await sem.WaitAsync();
    Task t = Task.Run(async () => 
    {
        var availabilityResponse = await client.QueryAvailability(serviceCopy));    
        // do your other stuff here with the result of QueryAvailability
    }
    t.ContinueWith(sem.Release());
}