问题描述
我想与他人仔细检查这是否是创建开始异步过程的扩展方法并返回一个函数的正确方法,该函数在调用时实质上等待该过程并获取结果。
public static Func<R> HandleInvoke<T,R>(this Func<T,R> function,T arg,Action<IAsyncResult> callback)
{
IAsyncResult result = function.BeginInvoke(arg,new AsyncCallback(callback),function);
return delegate
{
return function.EndInvoke(result);
};
}
本质上,我想这样使用(伪代码):
Func<R> myFunc = (some delegate).HandleInvoke(arg,callback);
// at this point the operation begins,but will be nonblocking
// do other stuff
var result = myFunc(); // Now I am deciding to wait on the result,which is blocking
不知道是否需要在这种情况下等待WaitHandles。也不确定是否甚至需要传递回调。我也认为这构成了关闭?
编辑
最后,
public static Func<R> HandleInvoke<T,T arg)
{
IAsyncResult asyncResult = function.BeginInvoke(arg,iAsyncResult =>
{
if (!(iAsyncResult as AsyncResult).EndInvokeCalled)
{
(iAsyncResult.AsyncState as Func<T,R>).EndInvoke(iAsyncResult);
}
},function);
return delegate
{
WaitHandle.WaitAll(new WaitHandle[] { asyncResult.AsyncWaitHandle });
return function.EndInvoke(asyncResult);
};
}
这似乎运作良好。回调检查是否已调用EndInvoke,如果未调用,则调用它。否则,在返回的委托中调用EndInvoke。
第二编辑
这是我的最新尝试-尚未向我抛出任何错误,并且似乎处理得很好。我无法在委托返回函数.EndInvoke()结果的地方使用它,但是委托在返回R之前一直等到匿名回调中已调用EndInvoke为止。Thread.Sleep()可能不是最佳解决方案。也可以使用更多检查来确保在每种情况下都已实际分配R。
public static Func<R> HandleInvoke<T,T arg)
{
R r = default(R);
IAsyncResult asyncResult = function.BeginInvoke(arg,result =>
{
r = (result.AsyncState as Func<T,R>).EndInvoke(result);
},function);
return delegate
{
while (!(asyncResult as AsyncResult).EndInvokeCalled)
{
Thread.Sleep(1);
}
return r;
};
}
解决方法
这应该可以,但是我不喜欢设计...这是基本问题。
如果调用了myFunc,则不应在回调中调用EndInvoke,但如果不调用myFunc,则由于您不在乎返回值,则必须在回调中调用EndInvoke。这使得使用该API不明显且容易出错。
有了睡眠,它就很活泼,尽管它不太可能经常咬你。这使用适当的同步原语来保证一切都会以正确的顺序发生。这是未经测试的代码,但应该可以工作
public static Func<R> HandleInvoke<T,R>(this Func<T,R> function,T arg)
{
R retv = default(R);
bool completed = false;
object sync = new object();
IAsyncResult asyncResult = function.BeginInvoke(arg,iAsyncResult =>
{
lock(sync)
{
completed = true;
retv = function.EndInvoke(iAsyncResult);
Monitor.Pulse(sync); // wake a waiting thread is there is one
}
},null);
return delegate
{
lock (sync)
{
if (!completed) // if not called before the callback completed
{
Monitor.Wait(sync); // wait for it to pulse the sync object
}
return retv;
}
};
}
,我遇到了一种更简单的方法,在该方法中,您可以从调用站点(而不是在后台线程上)抛出(重新)抛出异常,从而受益匪浅:
public static Func<R> Future<T,R> func,T arg)
{
IAsyncResult result = func.BeginInvoke(arg,null,null);
return () => func.EndInvoke(result);
}
对于一个动作,它几乎是相同的:
public static Action Future<T>(this Action<T> action,T arg)
{
IAsyncResult result = action.BeginInvoke(arg,null);
return () => action.EndInvoke(result);
}
但是,如果您不需要结果,则ThreadPool.QueueUserWorkItem()会更高效。