【发布时间】:2016-11-22 17:39:47
【问题描述】:
我有一个定义异步操作的合同,但我从一个未设置为异步的区域调用它。我想同时进行多个调用,所以我从这个开始:
var tasks = inputs.Select(input => service.GetResult(input));
var results = tasks.WhenAll(tasks).Result;
我认为这将并行启动所有调用,然后在第二行等待。但是,查看目标服务的日志记录,我发现调用是串行的。
我发现this article 显示了类似的调用我的方法并解释它不一定并行运行,所以我只是将其切换为直接并行调用以进行测试:
var results = new ConcurrentBag<Result>();
Parallel.ForEach(inputs, input => results.Add(service.GetResult(input).Result));
这按预期工作 - 我可以看到调用是并行进入服务的。
所以,这给我带来了两个问题:
1) 使用选项 2 的缺点是什么?
2) 如何让选项 1 正常工作?
这里有几个服务可以复制这个问题。以 WCFTestClient 和四个整数(1、2、3、4)的列表为例调用 ClientService。 (运行时可能需要更改端口。)
目标服务:
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
namespace AsyncNotParallel
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class TargetService : ITargetService
{
public async Task<int> GetResult(int input)
{
Trace.WriteLine($"In: {input}");
await Task.Delay(1000); // Do stuff.
Trace.WriteLine($"Out: {input}");
return input;
}
}
[ServiceContract]
public interface ITargetService
{
[OperationContract]
Task<int> GetResult(int input);
}
}
客户服务:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
namespace AsyncNotParallel
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class ClientService : IClientService
{
public int GetResults(List<int> inputs)
{
// Option 1:
var tasks = inputs.Select(input => Execute((ITargetService service) => service.GetResult(input)));
var results1 = Task.WhenAll(tasks).Result.Sum();
// Option 2:
var bag = new ConcurrentBag<int>();
Parallel.ForEach(inputs, input => bag.Add(Execute((ITargetService service) => service.GetResult(input)).Result));
var results2 = bag.Sum();
return results1 + results2;
}
private TResult Execute<TService, TResult>(Func<TService, TResult> operation)
{
var address = new EndpointAddress("http://localhost:34801/TargetService.svc");
var binding = new BasicHttpBinding();
var factory = new ChannelFactory<TService>(binding, address);
var channel = factory.CreateChannel();
var result = operation(channel);
((IClientChannel)channel).Close();
return result;
}
}
[ServiceContract]
public interface IClientService
{
[OperationContract]
int GetResults(List<int> inputs);
}
}
【问题讨论】:
-
永远不要使用.Result,总是等待异步调用。
-
@Bart:为了“从不使用 .Result”,我需要将整个客户端更改为异步/等待,但这是我获得异步合同的唯一位置。我可以制作自己的本地合同副本(在共享 DLL 中),然后进行 Parallel 调用,但这并不能回答我的问题,也不能帮助我理解这里发生的事情。
-
asycn 和并行不是一回事。 Asycn 是关于在 IO 发生时不阻塞线程,而并行是关于在多个线程上运行。有一些重叠之处在于您可以异步等待另一个线程完成,但异步的主要目的是防止线程被不必要地阻塞。
-
@juharr:确实如此。但是,我的印象也是,在阻塞当前线程等待完成之前启动几个异步调用将有效地使调用是并行的。你说这不正确吗?
-
如果没有好的minimal reproducible example,就不可能确定,但在我看来,发布的答案可能包括关于
service是什么的正确推断(基于您问题中的标签)和您正在执行代码的环境。更改并发模式应该可以解决您的主要问题。如果您需要更具体的帮助,您需要提供更好的问题。
标签: c# wcf asynchronous parallel-processing