65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 中快速异步委托

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (14投票s)

2009 年 7 月 1 日

CPOL

2分钟阅读

viewsIcon

54254

downloadIcon

372

实现了在异步操作期间比 .NET 中默认委托快得多的委托

引言

本文介绍如何实现一个委托,在BeginInvoke/EndInvoke操作期间比 CLR 注入的默认实现的BeginInvoke/EndInvoke委托性能更好。 这个想法很简单,没有作弊的 IL 和函数指针。

背景

在 .NET 中,有 3 种方法可以异步执行操作或任务。 第一种方法是使用 System.Threading.ThreadPool,这在大多数情况下最合适。 第二种方法是 Thread.Start,它比 ThreadPool 更适合长时间运行的操作。 最后一种是 APM 模式 (BeginSomething/EndSomething 方法对),由所有委托和一些主要类提供,例如 StreamWebRequest 等。 当您需要检索异步操作的结果时,APM 模式最适合。

让我们更仔细地考虑 .NET 中的委托。 .NET 中的每个委托都由 C# 编译器使用诸如 BeginInvokeEndInvoke 之类的方法进行修饰。 这些方法在某种程度上是 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);
}

这是输出

FastAsyncDelegates

现在你可以明白我为什么称我的实现 "快速"...

实现

一切都非常简单。 没有作弊的 IL,没有函数指针。 我所做的就是创建了一个简单的常规 .NET 委托,并用两个扩展方法 - BeginInvokeFastEndEnvokeFast 装饰它。 这就是故事。 这里最有趣的部分是 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 日:首次发布
© . All rights reserved.