.NET 中快速异步委托






4.31/5 (14投票s)
实现了在异步操作期间比 .NET 中默认委托快得多的委托
引言
本文介绍如何实现一个委托,在BeginInvoke
/EndInvoke
操作期间比 CLR 注入的默认实现的BeginInvoke
/EndInvoke
委托性能更好。 这个想法很简单,没有作弊的 IL 和函数指针。
背景
在 .NET 中,有 3 种方法可以异步执行操作或任务。 第一种方法是使用 System.Threading.ThreadPool
,这在大多数情况下最合适。 第二种方法是 Thread.Start
,它比 ThreadPool
更适合长时间运行的操作。 最后一种是 APM 模式 (BeginSomething
/EndSomething
方法对),由所有委托和一些主要类提供,例如 Stream
、WebRequest
等。 当您需要检索异步操作的结果时,APM 模式最适合。
让我们更仔细地考虑 .NET 中的委托。 .NET 中的每个委托都由 C# 编译器使用诸如 BeginInvoke
和 EndInvoke
之类的方法进行修饰。 这些方法在某种程度上是 abstract
- C# 编译器在编译时没有提供任何实现。 实现稍后由 CLR 注入。 当我们需要检索异步操作的结果或不存在结果(称为异常)时,BeginInvoke
/EndInvoke
似乎是完美的选择。 但这里的“完美”一词仅在逻辑设计的范围内有效。 当涉及到性能时,请小心:“尽可能避免使用 BeginInvoke
/EndInvoke
方法。 原因是这两种方法内部都使用远程处理基础结构。” - 我最近在 Microsoft 的一本书中遇到了这句话(我稍微改写了一下)。 基本上,我感兴趣的是,这两个方法比 ThreadPool
慢多少。 因此,我实现了我自己的 BeginInvoke
/EndInvoke
版本。
Using the Code
我的 APM 模式实现的使用方式与默认的相同。 这是一个例子
var fastDel = new FastInvokeDelegate<int>(() => MyAsyncTask(100));
var asyncResult = fastDel.BeginInvokeFast(null, null);
//do something else in parallel
int result = fastDel.EndInvokeFast(asyncResult);
这里 MyAsyncOperation
是一个 "无关紧要" 的函数,它返回一个整数。 正如我提到的,BeginInvokeFast
/EndInvokeFast
的使用方式与 BeginInvoke
/EndInvoke
完全相同。 这里的 FastInvokeDelegate
只是一个简单的常规委托,用扩展方法 BeginInvokeFast
/EndInvokeFast
修饰。 看起来有点笨拙。 不幸的是,C# 编译器目前没有提供任何更好的语言构造使其看起来更漂亮。
基准测试
我准备了一小段代码用于测试目的
static void Main(string[] args)
{
Func<int,int > del = (i) => 100 + i;
var fastDel = new FastInvokeDelegate<int >(() => del(100));
Stopwatch stopWatch = new Stopwatch();
var asyncResults = new List<iasyncresult >(10000);
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
asyncResults.Add(del.BeginInvoke(i, null, null));
}
stopWatch.Stop();
Console.WriteLine("Delegate.BeginInvoke(): {0}", stopWatch.ElapsedTicks);
Thread.Sleep(10000);
stopWatch.Reset();
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
del.EndInvoke(asyncResults[i]);
}
stopWatch.Stop();
Console.WriteLine("Delegate.EndInvoke(): {0}", stopWatch.ElapsedTicks);
asyncResults = new List<iasyncresult >(10000);
GC.Collect();
stopWatch.Reset();
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
asyncResults.Add(fastDel.BeginInvokeFast(null, null));
}
stopWatch.Stop();
Console.WriteLine("FastInvokeDelegate.BeginInvoke(): {0}", stopWatch.ElapsedTicks);
Thread.Sleep(10000);
stopWatch.Reset();
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
var res = fastDel.EndInvokeFast(asyncResults[i]);
}
stopWatch.Stop();
Console.WriteLine("FastInvokeDelegate.EndInvoke(): {0}", stopWatch.ElapsedTicks);
}
这是输出
现在你可以明白我为什么称我的实现 "快速"...
实现
一切都非常简单。 没有作弊的 IL,没有函数指针。 我所做的就是创建了一个简单的常规 .NET 委托,并用两个扩展方法 - BeginInvokeFast
和 EndEnvokeFast
装饰它。 这就是故事。 这里最有趣的部分是 IAsyncResult
实现,它直到需要时才初始化 ManualResetEvent
。
public delegate T FastInvokeDelegate<t>();
public static class DelegateExtensions
{
public static IAsyncResult BeginInvokeFast<t>
(this FastInvokeDelegate<t> del, object state, AsyncCallback callback)
{
return new FastInvokeAsyncResult<t>(del, callback, state);
}
public static T EndInvokeFast<t>(this FastInvokeDelegate<t> del,
IAsyncResult asyncResult)
{
var result = asyncResult as FastInvokeAsyncResult<t>;
if (result == null)
throw new InvalidOperationException("Wrong async result");
return result.End();
}
private class FastInvokeAsyncResult<t> : IAsyncResult
{
private volatile int m_isCompleted; // 0==not complete, 1==complete.
private ManualResetEvent m_asyncWaitHandle;
private volatile int m_asyncWaitHandleNeeded = 0; //0 - is not needed, 1 - needed
private readonly AsyncCallback m_callback;
private readonly object m_asyncState;
// To hold the results, exceptional or ordinary.
private Exception m_exception;
private T m_result;
public FastInvokeAsyncResult(FastInvokeDelegate<t> work,
AsyncCallback callback, object state)
{
m_callback = callback;
m_asyncState = state;
Run(work);
}
public bool IsCompleted
{
get { return (m_isCompleted == 1); }
}
public bool CompletedSynchronously
{
get { return false; }
}
public WaitHandle AsyncWaitHandle
{
get
{
if (m_asyncWaitHandleNeeded == 1)
{
return m_asyncWaitHandle;
}
m_asyncWaitHandleNeeded = 1;
m_asyncWaitHandle = new ManualResetEvent(m_isCompleted == 1);
return m_asyncWaitHandle;
}
}
public object AsyncState
{
get { return m_asyncState; }
}
private void Run(FastInvokeDelegate<t> work)
{
ThreadPool.QueueUserWorkItem(delegate
{
try
{
m_result = work();
}
catch (Exception e)
{
m_exception = e;
}
finally
{
m_isCompleted = 1;
if (m_asyncWaitHandleNeeded == 1)
{
m_asyncWaitHandle.Set();
}
if (m_callback != null)
m_callback(this);
}
});
}
public T End()
{
if (m_isCompleted == 0)
{
AsyncWaitHandle.WaitOne();
AsyncWaitHandle.Close();
}
if (m_exception != null)
throw m_exception;
return m_result;
}
}
}
结论
FastBeginInvoke
/FastEndInvoke
的用例与 BeginInvoke
/EndInvoke
完全相同。 唯一的区别是性能。
历史
- 2009 年 7 月 1 日:首次发布