Асинхронные операции внутри асинхронной операции
Мои знания о многопоточности все еще довольно элементарны, поэтому я очень ценю некоторые советы здесь. У меня есть интерфейс IOperationInvoker (из WCF), который имеет следующие методы:
IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
Учитывая конкретную реализацию этого интерфейса, мне нужно реализовать тот же интерфейс, при этом вызывая базовую реализацию в отдельном потоке. (в случае, если вам интересно, почему конкретная реализация вызывает устаревший COM-объект, который должен находиться в другом состоянии квартиры).
На данный момент я делаю что-то вроде этого:
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);
}
}
Я думаю, что мне нужно обернуть возвращенный AsyncResult своим собственным, чтобы я мог вернуться к теме, которую мы надували... но, честно говоря, я немного не в себе. Есть указатели?
Большое спасибо,
Джеймс
1 ответ
Самый простой способ асинхронно реализовать синхронный метод - это поместить его в делегат и использовать BeginInvoke
а также EndInvoke
методы полученного делегата. Это запустит синхронный метод в потоке потоков и BeginInvoke
вернет IAsyncResult
реализации, так что вам не нужно реализовывать все это. Тем не менее, вам нужно переправить немного дополнительных данных в IAsyncResult
вернулся IOperationInvoker.InvokeEnd
, Вы могли бы сделать это легко, создав реализацию IAsyncResult
который делегирует все внутри IAsyncResult
, но имеет дополнительное поле для хранения делегата, чтобы при IAsyncResult
экземпляр передается InvokeEnd
, вы можете получить доступ к делегату для вызова EndInvoke
в теме.
Однако после более внимательного прочтения вашего вопроса я вижу, что вам нужно использовать явную ветку с настройками COM и т. Д.
Что вам нужно сделать, это правильно реализовать IAsyncResult
, Из этого следует почти все, так как IAsyncResult
будет содержать все биты, необходимые для синхронизации.
Вот очень простая, но не очень эффективная реализация IAsyncResult
, Он включает в себя все необходимые функции: передачу аргументов, событие синхронизации, реализацию обратного вызова, распространение исключений из асинхронной задачи и возвращение результата.
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();
}
}
Для правильности важно, чтобы клиентский код не мог видеть IAsyncResult
реализация, в противном случае они могут получить доступ к таким методам, как SignalException
неуместно или читать Result
преждевременно. Класс можно сделать более эффективным, не создавая WaitHandle
реализация (ManualResetEvent
в примере), если это не нужно, но это сложно сделать 100% правильно. Так же Thread
а также ManualResetEvent
можно и нужно утилизировать в End
реализация, как это должно быть сделано со всеми объектами, которые реализуют IDisposable
, И, очевидно, End
следует проверить, что он получил реализацию правильного класса, чтобы получить более приятное исключение, чем исключение приведения. Я пропустил эти и другие детали, поскольку они затеняют основную механику асинхронной реализации.