Les opérations asynchrones au sein d'une opération asynchrone
-
11-09-2019 - |
Question
Mes connaissances multi-threading est encore assez rudimentaire, donc apprécierais vraiment quelques conseils ici. Je une interface, IOperationInvoker (de WCF) qui a les méthodes suivantes:
IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
Étant donné une mise en œuvre concrète de cette interface, je dois implémenter la même interface, tout en appelant la mise en œuvre sous-jacente dans une discussion séparée. (Au cas où vous vous demandez pourquoi, le implmentation concret appelle un héritage objet COM qui doit être dans un état différent de l'appartement).
En ce moment, je fais quelque chose comme ceci:
public StaOperationSyncInvoker : IOperationInvoker {
IOperationInvoker _innerInvoker;
public StaOperationSyncInvoker(IOperationInvoker invoker) {
this._innerInvoker = invoker;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
Thread t = new Thread(BeginInvokeDelegate);
InvokeDelegateArgs ida = new InvokeDelegateArgs(_innerInvoker, instance, inputs, callback, state);
t.SetApartmentState(ApartmentState.STA);
t.Start(ida);
// would do t.Join() if doing syncronously
// how to wait to get IAsyncResult?
return ida.AsyncResult;
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
// how to call invoke end on the
// thread? could we have wrapped IAsyncResult
// to get a reference here?
return null;
}
private class InvokeDelegateArgs {
public InvokeDelegateArgs(IOperationInvoker invoker, object instance, object[] inputs, AsyncCallback callback, object state)
{
this.Invoker = invoker;
this.Instance = instance;
this.Inputs = inputs;
this.Callback = callback;
this.State = state;
}
public IOperationInvoker Invoker { get; private set; }
public object Instance { get; private set; }
public AsyncCallback Callback { get; private set; }
public IAsyncResult AsyncResult { get; set; }
public Object[] Inputs { get; private set; }
public Object State { get; private set; }
}
private static void BeginInvokeDelegate(object data)
{
InvokeDelegateArgs ida = (InvokeDelegateArgs)data;
ida.AsyncResult = ida.Invoker.InvokeBegin(ida.Instance, ida.Inputs, ida.Callback, ida.State);
}
}
Je pense que je dois conclure le AsyncResult retourné avec mon propre, donc je peux revenir au fil que nous avons ... mais SPOOLED Honnêtement, je suis un peu hors de ma profondeur. Tous les pointeurs?
Merci,
James
La solution
La meilleure façon de mettre en oeuvre un procédé synchrone est asynchrone pour le mettre dans un délégué, et en utilisant les méthodes de BeginInvoke
et EndInvoke
sur le délégué résultant. Cela exécutera la méthode synchrone sur un fil de threadpool et BeginInvoke
retournera une implémentation IAsyncResult
, de sorte que vous n'avez pas à mettre en œuvre le courage de celui-ci. Cependant, vous avez besoin de faire passer un peu de données supplémentaires dans le IAsyncResult
retourné par IOperationInvoker.InvokeEnd
. Vous pouvez le faire facilement en créant une mise en œuvre de IAsyncResult
que les délégués tout à un IAsyncResult
intérieur, mais a un champ supplémentaire pour contenir le délégué, de sorte que lorsque l'instance de IAsyncResult
est passé à InvokeEnd
, vous pouvez accéder au délégué à appeler EndInvoke
sur elle .
Cependant, après lecture plus attentive de votre question, je vois que vous avez besoin d'utiliser un fil explicite avec les paramètres COM etc.
Ce que vous devez faire est de mettre en œuvre correctement IAsyncResult
. Presque tout découle de cela, puisque le IAsyncResult
contiendra tous les bits nécessaires pour la synchronisation.
Voici un très simple, mais pas très efficace, la mise en œuvre de IAsyncResult
. Il englobe toutes les caractéristiques essentielles:. Arguments en passant, un événement de synchronisation, la mise en œuvre de rappel, des exceptions de tâche se propageant async et le résultat retour
using System;
using System.Threading;
class MyAsyncResult : IAsyncResult
{
object _state;
object _lock = new object();
ManualResetEvent _doneEvent = new ManualResetEvent(false);
AsyncCallback _callback;
Exception _ex;
bool _done;
int _result;
int _x;
public MyAsyncResult(int x, AsyncCallback callback, object state)
{
_callback = callback;
_state = state;
_x = x; // arbitrary argument(s)
}
public int X { get { return _x; } }
public void SignalDone(int result)
{
lock (_lock)
{
_result = result;
_done = true;
_doneEvent.Set();
}
// never invoke any delegate while holding a lock
if (_callback != null)
_callback(this);
}
public void SignalException(Exception ex)
{
lock (_lock)
{
_ex = ex;
_done = true;
_doneEvent.Set();
}
if (_callback != null)
_callback(this);
}
public object AsyncState
{
get { return _state; }
}
public WaitHandle AsyncWaitHandle
{
get { return _doneEvent; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public int Result
{
// lock (or volatile, complex to explain) needed
// for memory model problems.
get
{
lock (_lock)
{
if (_ex != null)
throw _ex;
return _result;
}
}
}
public bool IsCompleted
{
get { lock (_lock) return _done; }
}
}
class Program
{
static void MyTask(object param)
{
MyAsyncResult ar = (MyAsyncResult) param;
try
{
int x = ar.X;
Thread.Sleep(1000); // simulate lengthy work
ar.SignalDone(x * 2); // demo work = double X
}
catch (Exception ex)
{
ar.SignalException(ex);
}
}
static IAsyncResult Begin(int x, AsyncCallback callback, object state)
{
Thread th = new Thread(MyTask);
MyAsyncResult ar = new MyAsyncResult(x, callback, state);
th.Start(ar);
return ar;
}
static int End(IAsyncResult ar)
{
MyAsyncResult mar = (MyAsyncResult) ar;
mar.AsyncWaitHandle.WaitOne();
return mar.Result; // will throw exception if one
// occurred in background task
}
static void Main(string[] args)
{
// demo calling code
// we don't need state or callback for demo
IAsyncResult ar = Begin(42, null, null);
int result = End(ar);
Console.WriteLine(result);
Console.ReadLine();
}
}
Il est important pour l'exactitude que le code client ne peut pas voir la mise en œuvre de IAsyncResult
, sinon ils pourraient accéder à des méthodes comme SignalException
inappropriée ou lire Result
prématurément. La classe peut être de ne pas construire la mise en œuvre de WaitHandle
rendu plus efficace (ManualResetEvent
dans l'exemple) si elle n'est pas nécessaire, mais cela est difficile à obtenir 100%. En outre, le Thread
et ManualResetEvent
peuvent et doivent être éliminés dans la mise en œuvre de End
, comme cela devrait être fait avec tous les objets qui mettent en œuvre IDisposable
. Et de toute évidence, End
doit vérifier pour vous assurer qu'il a obtenu une mise en œuvre de la bonne classe pour obtenir une plus belle exception qu'une exception coulée. Je l'ai laissé ces derniers et d'autres détails comme ils obscurcissent les mécanismes essentiels de la mise en œuvre async.