8天玩转并行开发——第六天 异步编程模型

简介:

      在.net里面异步编程模型由来已久,相信大家也知道Begin/End异步模式和事件异步模式,在task出现以后,这些东西都可以被task包装

起来,可能有人会问,这样做有什么好处,下面一一道来。

 

一: Begin/End模式

1: 委托

    在执行委托方法的时候,我们常常会看到一个Invoke,同时也有一对你或许不常使用的BeginInvoke,EndInvoke方法对,当然Invoke方法

是阻塞主线程,而BeginInvoke则是另开一个线程。

class Program
    {
        static void Main(string[] args)
        {
            var func = new Func<string, string>(i => { return i + "i can fly"; });

            var state = func.BeginInvoke("yes,", Callback, func);

            Console.Read();
        }

        static void Callback(IAsyncResult async)
        {
            var result = async.AsyncState as Func<string, string>;

            Console.WriteLine(result.EndInvoke(async));
        }
    }

 

下面我们用task包装一下

class Program
    {
        static void Main(string[] args)
        {
            var func = new Func<string, string>(i =>
            {
                return i + "i can fly";
            });

            Task<string>.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "yes,", null).ContinueWith
                (i =>
                {
                    Console.WriteLine(i.Result);
                });

            Console.Read();
        }
    }

 

可以看出,task只要一句就搞定,体现了task的第一个优点:简洁。

 

2:流

    我们发现在Stream抽象类中提供了这样两对BeginRead/EndRead,BeginWrite/EndWrite(异步读写)的方法,这样它的n多继承类都可以

实现异步读写,下面举个继承类FileStream的例子。

static void Main(string[] args)
        {
            var path = "C://1.txt";

            FileStream fs = new FileStream(path, FileMode.Open);

            FileInfo info = new FileInfo(path);

            byte[] b = new byte[info.Length];

            var asycState = fs.BeginRead(b, 0, b.Length, (result) =>
            {
                var file = result.AsyncState as FileStream;

                Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b));

                file.Close();

            }, fs);

            Console.WriteLine("我是主线程,我不会被阻塞!");

            Console.Read();
        }

 

我们用task包装一下

static void Main(string[] args)
        {
            var path = "C://1.txt";

            FileStream fs = new FileStream(path, FileMode.Open);

            FileInfo info = new FileInfo(path);

            byte[] b = new byte[info.Length];

            Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None)
                .ContinueWith
                (i =>
                {
                    Console.WriteLine("文件内容:{0}", Encoding.Default.GetString(b));
                });

            Console.WriteLine("我是主线程,我不会被阻塞!");

            Console.Read();
        }

 

其实看到这里,我们并没有发现task还有其他的什么优点,但是深入的想一下其实并不是这么回事,task能够游刃于线程并发和同步,而原始的异步

编程要实现线程同步还是比较麻烦的。

 

     假如现在有这样的一个需求,我们需要从3个txt文件中读取字符,然后进行倒序,前提是不能阻塞主线程。如果不用task的话我可能会用工作线程

去监视一个bool变量来判断文件是否全部读取完毕,然后再进行倒序,我也说了,相对task来说还是比较麻烦的,这里我就用task来实现。

class Program
    {
        static byte[] b;

        static void Main()
        {
            string[] array = { "C://1.txt", "C://2.txt", "C://3.txt" };

            List<Task<string>> taskList = new List<Task<string>>(3);

            foreach (var item in array)
            {
                taskList.Add(ReadAsyc(item));
            }

            Task.Factory.ContinueWhenAll(taskList.ToArray(), i =>
            {
                string result = string.Empty;

                //获取各个task返回的结果
                foreach (var item in i)
                {
                    result += item.Result;
                }

                //倒序
                String content = new String(result.OrderByDescending(j => j).ToArray());

                Console.WriteLine("倒序结果:"+content);
            });

            Console.WriteLine("我是主线程,我不会被阻塞");

            Console.ReadKey();
        }

        //异步读取
        static Task<string> ReadAsyc(string path)
        {
            FileInfo info = new FileInfo(path);

            byte[] b = new byte[info.Length];

            FileStream fs = new FileStream(path, FileMode.Open);

            Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None);

            //返回当前task的执行结果
            return task.ContinueWith(i =>
            {
                return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty;
            }, TaskContinuationOptions.ExecuteSynchronously);
        }
    }

 

可以看出,task的第二个优点就是:灵活性。

 

这里可能就有人要问了,能不能用开多个线程用read以同步的形式读取,变相的实现文件异步读取,或许我们可能常听说程序优化后,最后出现的

瓶颈在IO上面,是的,IO是比较耗费资源的,要命的是如果我们开的是工作线程走IO读取文件,那么该线程就会一直处于等待状态,不会再接收任

何的外来请求,直到线程读取到文件为止,那么我们能不能用更少的线程来应对更多的IO操作呢?答案肯定是可以的,这里就设计到了”异步IO“的

概念,具体内容可以参照百科:http://baike.baidu.com/view/1865389.htm  ,有幸的是beginXXX,endXXX完美的封装了“异步IO”。

 

二:事件模式

   这个模式常以XXXCompleted的形式结尾,我们在文件下载这一块会经常遇到,这里我也举个例子。


class Program
    {
        static void Main(string[] args)
        {
            WebClient client = new WebClient();

            client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);

            client.DownloadFileAsync(new Uri("http://imgsrc.baidu.com/baike/abpic/item/6a600c338744ebf844a0bc74d9f9d72a6159a7ac.jpg"),
                                   "1.jpg", "图片下完了,你懂的!");

            Console.WriteLine("我是主线程,我不会被阻塞!");
            Console.Read();
        }

        static void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            Console.WriteLine("\n" + e.UserState);
        }
    }

 

先前也说了,task是非常灵活的,那么针对这种异步模型,我们该如何封装成task来使用,幸好framework中提供了TaskCompletionSource来帮助

我们快速实现。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Net;
using System.ComponentModel;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main()
        {
            var downloadTask = DownLoadFileInTask(
                    new Uri(@"http://www.7720mm.cn/uploadfile/2010/1120/20101120073035736.jpg")
                    , "C://1.jpg");

            downloadTask.ContinueWith(i =>
            {
                Console.WriteLine("图片:" + i.Result + "下载完毕!");
            });

            Console.WriteLine("我是主线程,我不会被阻塞!");

            Console.Read();
        }

        static Task<string> DownLoadFileInTask(Uri address, string saveFile)
        {
            var wc = new WebClient();

            var tcs = new TaskCompletionSource<string>(address);

            //处理异步操作的一个委托
            AsyncCompletedEventHandler handler = null;

            handler = (sender, e) =>
            {
                if (e.Error != null)
                {
                    tcs.TrySetException(e.Error);
                }
                else
                {
                    if (e.Cancelled)
                    {
                        tcs.TrySetCanceled();
                    }
                    else
                    {
                        tcs.TrySetResult(saveFile);
                    }
                }

                wc.DownloadFileCompleted -= handler;
            };

            //我们将下载事件与我们自定义的handler进行了关联
            wc.DownloadFileCompleted += handler;

            try
            {
                wc.DownloadFileAsync(address, saveFile);
            }
            catch (Exception ex)
            {
                wc.DownloadFileCompleted -= handler;

                tcs.TrySetException(ex);
            }

            return tcs.Task;
        }
    }
}


相关文章
|
7月前
|
设计模式 算法 程序员
代码之美:追求简洁高效的编程艺术
【2月更文挑战第16天】 在数字世界的构建中,编程不仅仅是一门科学,更是一种艺术。本文将探讨如何在编程实践中追求简洁与效率的完美结合,揭示编程中的美学原则和实用技巧。通过对设计模式、代码重构以及性能优化等关键技术概念的深入分析,我们旨在为开发者提供一套提升代码质量、实现技术突破的思维工具。
|
前端开发 测试技术 程序员
记一次迭代需求中的微型代码重构实战总结
本文基于日常需求中遇到的问题,演绎微型重构的过程。
133543 28
|
3月前
|
存储 Java 关系型数据库
“代码界的魔法师:揭秘Micronaut框架下如何用测试驱动开发将简单图书管理系统变成性能怪兽!
【9月更文挑战第6天】Micronaut框架凭借其轻量级和高性能特性,在Java应用开发中备受青睐。本文通过一个图书管理系统的案例,介绍了在Micronaut下从单元测试到集成测试的全流程。首先,我们使用`@MicronautTest`注解编写了一个简单的`BookService`单元测试,验证添加图书功能;接着,通过集成测试验证了`BookService`与数据库的交互。整个过程展示了Micronaut强大的依赖注入和测试支持,使测试编写变得更加高效和简单。
82 4
|
4月前
|
前端开发 开发者 Python
Flask框架之谜:如何用Blueprints神技轻松制胜模块化应用?
【8月更文挑战第31天】随着Flask应用规模扩大,代码管理和模块化变得至关重要。Blueprints作为Flask提供的强大工具,支持将应用分割成包含独立路由、视图、模板和静态文件的模块,从而提高代码清晰度与重用性。本文通过创建前端和后台管理两个蓝图的示例,展示了如何在Flask中使用Blueprints实现应用模块化,简化大型应用开发与维护工作,提升代码结构的清晰度及团队协作效率。
32 0
|
5月前
|
开发框架 前端开发 关系型数据库
Winform开发的快速、健壮、解耦的几点建议
Winform开发的快速、健壮、解耦的几点建议
编程问题之响应式编程使用了哪些技术
编程问题之响应式编程使用了哪些技术
|
7月前
|
开发者 Python
模块化编程:高效编程之道
模块化编程:高效编程之道
|
设计模式 Java 数据库连接
解锁设计模式的神秘面纱:编写无懈可击的代码之原型设计模式
解锁设计模式的神秘面纱:编写无懈可击的代码之原型设计模式
45 1
|
小程序 JavaScript 前端开发
兴趣编程六步法
欢迎来到我的小院,在当今时代,科技力量代表一个国家的核心竞争力,其中计算机编程技术尤为重要,可以从中学习逻辑分析能力,业务抽象能力,专注思考能力等等,美国等一些发达国家,已经把编程教育纳入小学课本中,所以我们也需要加快步伐,掌握编程的一些理念和实战技巧。
兴趣编程六步法