Ich habe einen Vertrag, der eine asynchrone Operation definiert, aber ich rufe es aus einem Bereich, der nicht als async eingerichtet ist. Ich möchte mehrere Anrufe parallel machen, und so begann ich mit diesem:async, aber nicht parallel
var tasks = inputs.Select(input => service.GetResult(input));
var results = tasks.WhenAll(tasks).Result;
dachte ich, das würde alle Anrufe parallel beginnen und dann in der zweiten Zeile warten. Bei der Protokollierung des Zieldienstes stellte sich jedoch heraus, dass die Anrufe seriell eingehen.
fand ich this article, die eine ähnliche Methode des Aufrufs zu Mine zeigt und erklärt, dass es nicht unbedingt parallel laufen, so wechselte ich es nur auf einen gerade parallel Aufruf Test:
var results = new ConcurrentBag<Result>();
Parallel.ForEach(inputs, input => results.Add(service.GetResult(input).Result));
Dies funktioniert wie erwartet - ich kann sehen, dass die Anrufe parallel zum Dienst kommen.
Also, das bringt mich auf zwei Fragen:
1) Was sind die Nachteile von 2 mit Option gehen?
2) Wie würde ich Option 1 richtig funktionieren lassen?
Hier sind ein paar Dienste, um das Problem zu replizieren. Rufen Sie ClientService mit WCFTestClient und eine Liste von vier Ints (1, 2, 3, 4) als ein Beispiel auf. (Port wird wahrscheinlich ändern müssen, wenn Sie es ausführen.)
target:
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);
}
}
ClientService:
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);
}
}
Verwenden Sie niemals .Result, warten Sie immer auf asynchrone Aufrufe. – Bart
@Bart: Um ".Result" nie zu verwenden, müsste ich den gesamten Client async/abwarten, aber das ist der einzige Ort, an dem ich einen Async-Vertrag habe. Ich kann meine eigene lokale Kopie des Vertrags machen (das ist in einer geteilten DLL) und einfach mit dem parallelen Anruf gehen, aber das beantwortet meine Frage nicht und hilft mir nicht zu verstehen, was hier passiert. – zimdanen
asycn ist nicht das Gleiche wie parallel. Asycn blockiert einen Thread nicht, während IO auftritt, während parallel über mehrere Threads ausgeführt wird. Es gibt einige Überlappungen, in denen Sie asynchron warten können, bis ein anderer Thread beendet ist, aber der Hauptzweck von async besteht darin, Threads davon abzuhalten, unnötigerweise blockiert zu werden. – juharr