J'ai généré un proxy avec de la tâche en fonction des opérations.
Comment ce service devrait-il être appelé correctement (élimination de l' ServiceClient
et de la OperationContext
par la suite) à l'aide de async/await?
Ma première tentative a été:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
Cours d' ServiceHelper
d'une classe qui crée l' ServiceClient
et de la OperationContextScope
, et dispose d'entre eux par la suite:
try
{
if (_operationContextScope != null)
{
_operationContextScope.Dispose();
}
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_operationContextScope = null;
_serviceClient = null;
}
Cependant, cela a échoué lamentablement lors de l'appel de deux services en même temps avec l'erreur suivante: "Ce OperationContextScope est disposé sur un autre fil qu'il a été créé."
MSDN dit:
Ne pas utiliser l'asynchrone "en attente de" motif à l'intérieur d'un OperationContextScope bloc. Lorsque la poursuite a lieu, il peut s'exécuter sur un thread différent et OperationContextScope est thread spécifique. Si vous devez appeler le "en attente" pour un appel asynchrone, utilisation à l'extérieur de la OperationContextScope bloc.
Donc, c'est ça le problème! Mais, comment pouvons-nous résoudre correctement?
private async void DoStuffWithDoc(string docId)
{
var doc = await GetDocumentAsync(docId);
if (doc.YadaYada)
{
// more code here
}
}
public Task<Document> GetDocumentAsync(string docId)
{
var docClient = CreateDocumentServiceClient();
using (new OperationContextScope(docClient.InnerChannel))
{
return docClient.GetDocumentAsync(docId);
}
}
Mon problème avec son code, c'est qu'il n'appelle jamais Fermer (ou Abandonner) sur le ServiceClient.
J'ai aussi trouvé un moyen de propagation de l' OperationContextScope
aide personnalisée SynchronizationContext
. Mais, outre le fait que c'est beaucoup de "risqué" de code, il affirme que:
Il est intéressant de noter que il possède quelques petites questions au sujet de l'élimination de l'opération-cadre étendues (puisqu'ils ne vous permettent de les disposer sur le thread appelant), mais cela ne semble pas être un problème depuis (au moins selon le démontage), ils mettent en œuvre Dispose (), mais pas Finalize().
Alors, sommes-nous hors de la chance ici? Est-il un schéma éprouvé pour appeler des services WCF avec async/await ET de disposer à la FOIS de l' ServiceClient
et de la OperationContextScope
? Peut-être quelqu'un formulaire Microsoft (peut-être le gourou de Stephen Toub :)) peut vous aider.
Merci!
[Mise à JOUR]
Avec beaucoup d'aide de la part de l'utilisateur Noseratio, je suis venu avec quelque chose qui fonctionne: ne pas utiliser OperationContextScope
. Si vous l'utilisez pour une de ces raisons, essayez de trouver une solution qui convient à votre scénario. Sinon, si vous avez vraiment, vraiment, besoin OperationContextScope
, vous aurez à venir avec une mise en œuvre d'un SynchronizationContext
qui le capture, et qui semble très dur (si possible, il doit y avoir une raison pour laquelle ce n'est pas le comportement par défaut).
Donc, le travail plein de code est:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
Avec ServiceHelper
être:
public class ServiceHelper<TServiceClient, TService> : IDisposable
where TServiceClient : ClientBase<TService>, new()
where TService : class
{
protected bool _isInitialized;
protected TServiceClient _serviceClient;
public TServiceClient Proxy
{
get
{
if (!_isInitialized)
{
Initialize();
_isInitialized = true;
}
else if (_serviceClient == null)
{
throw new ObjectDisposedException("ServiceHelper");
}
return _serviceClient;
}
}
protected virtual void Initialize()
{
_serviceClient = new TServiceClient();
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
try
{
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_serviceClient = null;
}
}
}
}
Notez que la classe prend en charge l'extension; peut-être vous avez besoin d'hériter et de fournir des informations d'identification.
Le seul "piège" est que, en GetHomeInfoAsync
, vous ne pouvez pas il suffit de retourner l' Task
vous obtenez à partir du proxy (ce qui devrait sembler naturel, pourquoi créer un nouveau Task
lorsque vous en avez déjà un). Eh bien, dans ce cas, vous avez besoin d' await
le proxy Task
et ensuite fermer (ou abandon) le ServiceClient
, sinon, vous serez le fermer tout de suite après l'invocation du service (alors que les octets sont envoyés sur le fil)!
OK, nous avons un moyen de le faire fonctionner, mais il serait bien d'obtenir une réponse à partir d'une source faisant autorité, comme Noseratio unis.