.NET(C#) TPL:TaskFactory.FromAsync与委托的异步调用

返回目录

1. 简单的委托异步调用

看一个非常简单的C#委托异步调用:

static void Main()

{

    //定义委托

    var del = new Func<stringcharint>(doo);

    //调用BeginInvoke

    del.BeginInvoke("a"'b', callback, del);

   

    Console.ReadKey();

}

static void callback(IAsyncResult ar)

{

    //从AsyncState中提取委托

    var del = (Func<stringcharint>)ar.AsyncState;

    Console.WriteLine("callback调用");

    //调用EndInvoke

    var res = del.EndInvoke(ar);

    Console.WriteLine("doo返回值:{0}", res);

}

//异步执行方法

static int doo(string a, char b)

{

    Console.WriteLine("doo调用:{0} {1}", a, b);

    return 1;

}

上述代码会输出:

doo调用:a b

doo返回值:1

doo被异步调用,同时EndInvoke方法被调用后,其返回值被输出。

上面的代码存在异步调用中常见的一些繁琐问题:

  • 必须把委托作为参数传入到回调方法中(当然使用Lambda或者匿名委托捕获外部变量可以避免)
  • 必须在回调方法中调用委托的EndInvoke,并且传入IAsyncResult参数从而使异步调用完成。

.NET 4.0 TPL中的TaskFactory.FromAsync可以很大程度上简化异步操作,于是上面的代码简化成这样:

static void Main()

{

    var del = new Func<stringcharint>(doo);

    Task<int>.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, "a"'b'null)

        .ContinueWith(t => Console.WriteLine("doo返回值:{0}", t.Result));

    Console.ReadKey();

}

//异步执行方法

static int doo(string a, char b)

{

    Console.WriteLine("doo调用:{0} {1}", a, b);

    return 1;

}

注意FromAsync是泛型方法,会根据BeginInvoke委托来匹配后面的参数类型(事实上这是Visual Studio的功劳),那么实际上面的FromAsync完整泛型调用是这样的:

Task<int>.Factory.FromAsync<stringchar>(del.BeginInvoke, del.EndInvoke, "a"'b'null)

    .ContinueWith(t => Console.WriteLine("doo返回值:{0}", t.Result));

注意此时不需要传入异步调用的而外参数,所以FromAsync的state参数总是为null。

返回目录

2. 带有异常的委托异步调用

对于带有异常的委托异步调用,同样,我们必须调用委托对象的EndInvoke来捕获异常,如下代码:

static void Main()

{

    var del = new Action(doo);

    del.BeginInvoke(callback, del);

    Console.ReadKey();

}

//回调方法

static void callback(IAsyncResult ar)

{

    Console.WriteLine("callback调用");

    var del = (Action)ar.AsyncState;

    try

    {

        del.EndInvoke(ar);

    }

    catch (Exception ex)

    {

        Console.WriteLine("捕获doo异常:{0}", ex.Message);

    }

}

//异步执行方法

static void doo()

{

    Console.WriteLine("doo调用");

    throw new Exception("doo错误");

}

代码会输出:

doo调用

callback调用

捕获doo异常:doo错误

现在我们一TPL的方式简化操作:

static void Main()

{

    var del = new Action(doo);

    Task.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, null);

    Console.ReadKey();

}

//异步执行方法

static void doo()

{

    Console.WriteLine("doo调用");

    throw new Exception("doo错误");

}

输出很有意思,竟然是:

doo调用

没有异常抛出,但是doo方法又确实运行了!原因则是Task运行对异常处理的特殊机制,读者可以参考我的另一篇文章:

.NET(C#) TPL:Task中未觉察异常和TaskScheduler.UnobservedTaskException事件

这个异常可以通过强行垃圾回收,此时这个未觉察状态的异常会在垃圾回收时终结器执行线程中被抛出。如下代码(修改Main方法如下,其他代码不变):

var del = new Action(doo);

Task.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, null);

//确保任务完成

Thread.Sleep(1000);

//强制垃圾会受到

GC.Collect();

//等待终结器处理

GC.WaitForPendingFinalizers();

输出:

doo调用

Unhandled Exception: System.AggregateException: A Task's exception(s) were not o

bserved either by Waiting on the Task or accessing its Exception property. As a

result, the unobserved exception was rethrown by the finalizer thread. ---> Syst

em.Exception: doo错误

Server stack trace:

   at Mgen.Program.doo() in E:\Users\Mgen\Documents\Visual Studio 2010\Projects\

ConsoleApplication1\ConsoleApplication1\Program.cs:line 34

...(省略)

这个异常会被抛出。

当然根据本例,如果想捕获异常,可以使用Task.Wait来等待Task结束并通过AggregateException.InnerExceptions接收异常:

var del = new Action(doo);

var task = Task.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, null);

try

{

    task.Wait();

}

catch (AggregateException ae)

{

    foreach (var ex in ae.InnerExceptions)

        Console.WriteLine("doo方法异常:{0}", ex.Message);

}

不过上面的代码我的方法调用就不是异步的了,因此更好的方法还是使用ContinueWith,等Task结束后,通过Task.Exception(同样返回AggregateException对象)来枚举异常,如下代码:

var del = new Action(doo);

Task.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, null)

    .ContinueWith(t =>

        {

            foreach(var ex in t.Exception.InnerExceptions)

                Console.WriteLine("doo方法异常:{0}", ex.Message);

        });

Console.ReadKey();

输出正确:

doo调用

doo方法异常:doo错误

上面都是讲怎样捕获异常,如果你想忽略这个异常(同样不想让这个异常在垃圾回收时在终结器执行线程中被抛出)可以简单的引用一下Exception属性,TaskContinuationOptions.OnlyOnFaulted来使引用Exception属性只发生在发生异常时(即Exception为null的时候没必要再去引用它)。就像这样:

var del = new Action(doo);

Task.Factory.FromAsync(del.BeginInvoke, del.EndInvoke, null)

    .ContinueWith(t => { var exp = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);

Console.ReadKey();

返回目录

3. 有ref或out的委托异步调用

对于此类委托异步调用,由于委托泛型定义不支持ref和out参数,所以此类委托异步调用无法用FromAsync完成,只能用原始委托异步调用来做。

代码:

delegate void MyDelegate(string a, out int b);

static void Main()

{

    var del = new MyDelegate(doo);

    int b;

    var ar = del.BeginInvoke("a"out b, callback, del);

    Console.ReadKey();

}

static void callback(IAsyncResult ar)

{

    var del = (MyDelegate)ar.AsyncState;

    Console.WriteLine("callback调用");

    int res;

    del.EndInvoke(out res, ar);

    Console.WriteLine("doo out参数值:{0}", res);

}

输出:

doo调用

callback调用

doo out参数值:71

返回目录

4. .NET Framework中预定义的异步调用

.NET Framework中也定义了许多类似委托异步调用的方法,其实此类异步调用又称APM:异步编程模式(全称Asynchronous Programming Model),此类异步方法的命名以Beginxxx和Endxxx。TaskFactory.FromAsync处理.NET Framework中预定义的异步调用同样游刃有余。

原因是TaskFactory.FromAsync定义了诸多重载可以满足多数预定义的异步调用,比如下面是一个比较长的重载:

//TaskFactory.FromAsync的重载之一

public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(Func<TArg1, TArg2, TArg3, AsyncCallbackobjectIAsyncResult> beginMethod,Func<IAsyncResult, TResult> endMethod, TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions);

我们就以Stream类型的异步读取做例子,不用FromAsync的话这样写:

var ms = new MemoryStream(new byte[] { 123 });

byte[] data = new byte[10];

ms.BeginRead(data, 0, data.Length, ir =>

    {

        var read = ms.EndRead(ir);

        Console.WriteLine(BitConverter.ToString(data.Take(read).ToArray()));

    }, null);

Console.ReadKey();

而使用FromAsync:

var ms = new MemoryStream(new byte[] { 123 });

byte[] data = new byte[10];

var task = Task<int>.Factory.FromAsync(ms.BeginRead, ms.EndRead, data, 0, data.Length, null)

    .ContinueWith(t => Console.WriteLine(BitConverter.ToString(data.Take(t.Result).ToArray())));

Console.ReadKey();

后者更精短。

:D

作者:Mgen

本文版权归作者所有,欢迎以网址(链接)的方式转载,不欢迎复制文章内容的方式转载,其一是为了在搜索引擎中去掉重复文章内容,其二复制后的文章往往没有提供本博客的页面格式和链接,造成文章可读性很差。望有素质人自觉遵守上述建议。

如果一定要以复制文章内容的方式转载,必须在文章开头标明作者信息和原文章链接地址。否则保留追究法律责任的权利。

当前标签: BCL

共4页: 1 2 3 4 下一页 
.NET(C#) TPL:TaskFactory.FromAsync与委托的异步调用 _Mgen 2012-03-02 14:09 阅读:919 评论:0  
 
.NET(C#):设置文件系统对象的访问控制 _Mgen 2012-02-28 14:26 阅读:116 评论:0  
 
.NET(C#):ConcurrentBag<T>同线程元素的添加和删除 _Mgen 2012-02-21 13:45 阅读:27 评论:0  
 
.NET(C#) TPL:Parallel循环和多个Task的异常处理 _Mgen 2012-02-20 13:40 阅读:24 评论:0  
 
.NET(C#):DebuggerDisplay特性碉堡了! _Mgen 2012-02-19 11:33 阅读:30 评论:0  
 
 
.NET(C#):警惕PLINQ结果的无序性 _Mgen 2012-02-17 23:23 阅读:21 评论:0  
 
.NET(C#):DLR有趣的调用自己的动态对象 _Mgen 2012-02-17 20:28 阅读:21 评论:0  
 
.NET(C#):计算HttpWebResponse的下载速度 _Mgen 2012-01-30 16:34 阅读:84 评论:0  
 
.NET(C#):使用HttpWebRequest头中的Range下载文件片段 _Mgen 2012-01-30 10:38 阅读:88 评论:0  
 
.NET(C#):将数据字节大小转换成易读的单位字符串 _Mgen 2012-01-28 18:29 阅读:52 评论:0  
 
WPF:处理程序的“未处理异常” _Mgen 2012-01-07 23:38 阅读:58 评论:0  
 
.NET(C#):在KeyedCollection类型中加字典的TryGetValue方法 _Mgen 2012-01-07 18:31 阅读:59 评论:2  
 
[我的软件]Mgen 轻型任务管理器 1.0 _Mgen 2012-01-04 20:21 阅读:1617 评论:16  
 
Mgen.BasicTask V2 _Mgen 2012-01-04 16:52 阅读:51 评论:0  
 
Mgen.BasicTask V1 _Mgen 2012-01-01 20:09 阅读:178 评论:0  
 
.NET(C#):监控CPU和内存的使用状况 _Mgen 2011-12-28 18:17 阅读:411 评论:3  
 
[我的小软件]Mgen.TXT转EXE _Mgen 2011-12-26 23:07 阅读:1338 评论:6  
 
.NET(C#):关于部属程序集时的重定向版本 _Mgen 2011-12-25 17:20 阅读:68 评论:0  
 
.NET(C#):关于正确读取中文文本文件 _Mgen 2011-12-25 13:40 阅读:304 评论:0  
 
.NET(C#):正则表达式的RegexOptions和行数 _Mgen 2011-12-24 22:20 阅读:63 评论:0  
 
 
.NET(C#):从文件中觉察编码 _Mgen 2011-12-23 00:24 阅读:65 评论:0  
 
.NET(C#):两个进程防止被终止 _Mgen 2011-12-21 23:15 阅读:62 评论:0  
 
.NET(C#):关于进程退出的事件 _Mgen 2011-12-19 00:06 阅读:79 评论:0  
 
 
.NET(C#):不使用SMTP的25端口发邮件的权限检查 _Mgen 2011-12-17 15:54 阅读:42 评论:0  
 
 
.NET(C#):反射Emit生成WPF程序 _Mgen 2011-12-16 18:45 阅读:110 评论:0  
 
.NET(C#):反射Emit生成结构体 _Mgen 2011-12-16 16:30 阅读:38 评论:0  
 
 
.NET(C#):SecurityAction.RequestMinimum和RequestOptional _Mgen 2011-12-15 18:06 阅读:22 评论:0  
 
.NET(C#):网络域中的计算机的WMI查询 _Mgen 2011-12-15 17:16 阅读:71 评论:0  
 
.NET(C#):ComImport特性和ComVisible特性 _Mgen 2011-12-14 23:11 阅读:92 评论:0  
 
.NET(C#):有趣的IdentityReference和WindowsIdentity _Mgen 2011-12-13 17:19 阅读:41 评论:0  
 
.NET(C#):程序集配置的<codebase>元素和<probing>元素 _Mgen 2011-12-12 21:45 阅读:95 评论:0  
 
 
.NET(C#):觉察XML反序列化中的未知节点 _Mgen 2011-12-12 11:16 阅读:35 评论:0  
 
.NET(C#):使用ManagementEventWatcher进行WMI事件查询 _Mgen 2011-12-12 10:29 阅读:72 评论:2  
 
 
.NET(C#):浅谈程序集清单资源和RESX资源 _Mgen 2011-12-11 14:08 阅读:1201 评论:2  
 
.NET(C#):使用ResourceManager类型 _Mgen 2011-12-11 13:16 阅读:241 评论:0  
 
.NET(C#):使用IResourceReader,IResourceWriter和ResourceSet _Mgen 2011-12-11 00:56 阅读:62 评论:0  
 
共4页: 1 2 3 4 下一页 
原文地址:https://www.cnblogs.com/Leo_wl/p/2378695.html