栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

C#:异步编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C#:异步编程

异步操作表示执行某项操作之后不等待操作结束,但可以在操作结束后收到通知。

示例:

    public static class demo13
    {
        public static void Send(IPAddress address, int port)
        {
            var client = new TcpClient();
            try
            {
                client.Connect(address, port);
                client.GetStream().Write(Encoding.ASCII.GetBytes("Hello"));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                client.Close();
            }
        }
    }

示例是一个调用阻塞操作的例子。tcp连接的方法就是一个阻塞操作,只有连接成功或是失败的时候才会回复。发送数据也是同样的。线程阻塞的时候操作系统会调度这些执行操作的线程进入等待状态,等到操作完成后再重新把线程放入待运行队列中并调度执行。

这种方式会出现一个问题,如果我们要同时管理多个tcp连接,那么我们就需要创建很多的线程,每个线程分配的栈空间加起来就会消耗很多的内存。在linux平台下,一个线程栈会默认分配8~10M的空间,也就是说太多的线程就会导致内存空间不足。并且操作系统调度线程需要的时间也会变长,会导致性能下降。
 

Event Loop

解决多线程缺陷的方法之一就是使用Event Loop。

Event Loop事件循环机制。程序有一个或多个线程专门用于捕获对象检查状态,把执行的阻塞操作换为非阻塞操作,再注册事件以在处理完成后收到通知。js中的异步就是使用的事件循环机制实现的。

APM

异步编程模型,.NET提供的基于异步回调的操作。

.NET Core在不同的系统上使用不同的方式实现APM。在windows上是通过IOCP(一组支持多个同时发生异步I/O操作的接口)实现的;在Linux上是通过epoll(多路复用IO接口)实现的;而在mac上是通过kqueue接口实现的。虽然针对不同的系统使用的方式不同,但是实现出来的功能是相同的。

示例:

    public static class demo11
    {
        public static void Send(IPAddress address, int port)
        {
            var client = new TcpClient();
            // 开始异步连接
            client.BeginConnect(address,port,ar =>
            {
                // 无论成功还是失败都会调用这个回调
                try
                {
                    client.EndConnect(ar);
                }
                catch (Exception e) {
                    Console.WriteLine(e);
                    client.Close();
                }

                var bytes = Encoding.ASCII.GetBytes("Hello");
                var stream = client.GetStream();
                stream.BeginWrite(bytes, 0, bytes.Length, result =>
                {
                    try
                    {

                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                    finally
                    {
                        client.Close();
                    }
                },null);
            },null);
        }
    }

TPL

任务并行库。由于基于回调的异步操作的通用性不够强,.NET在此基础之上又封装了一层以任务(Task)为基础的接口。

TPL最大的特点就是分离了执行异步操作与注册回调的处理。这样一来,我们就可以让任何异步操作都具有相同的方法注册回调、等待结束和处理错误。并且为支持之后的async、await打下了基础。

示例:

    public static class demo12
    {
        public static void test(IPAddress address, int port)
        {
            // 创建1万个Task
            var tasks = new Task[10000];
            for (var i = 0; i < tasks.Length; i++)
            {
                // SendAsync 本身就是一个异步方法
                tasks[i] = SendAsync(address, port);
            }
            // 等待所有异步操作执行完成
            Task.WaitAll(tasks);
        }

        public static Task SendAsync(IPAddress address, int port)
        {
            var client = new TcpClient();
            // 异步连接
            var task = client.ConnectAsync(address, port);
            var a = new TaskFactory();

            // 异步发送数据
            var task1 = task.ContinueWith(t => {
                var bytes = Encoding.ASCII.GetBytes("Hello");
                var stream = client.GetStream();
                // 返回一个新的task
                // 这里会产生Task嵌套
                // 在外部使用Unwrap方法解包
                return stream.WriteAsync(bytes,0,bytes.Length);
            }).Unwrap();

            // 异步处理发送结果
            var task2 = task1.ContinueWith(t => {
                if (t.IsFaulted)
                {
                    // 失败
                }
                else if (t.IsCompleted)
                { 
                    // 完成
                }
                client.Close();
            });
            return task2;
        }
    }

async和await

由TPL支持,极大的简化了异步方法的编写。

async用于标记方法,标记的方法会使用await执行异步操作。然后把整个方法合并成一个异步操作。

示例:

    public static class demo14
    {
        // async关键字是给编译器看的,为了生产状态机
        public static async Task SendAsync(IPAddress address, int port)
        {
            var client = new TcpClient();
            try
            {
                // 异步连接
                await client.ConnectAsync(address,port);
                // 连接成功后会从这里继续开始执行
                // 失败时会抛出异常并在下面的catch块中被捕捉

                var bytes = Encoding.ASCII.GetBytes("Hello");
                var stream = client.GetStream();
                // 异步发送数据
                await stream.WriteAsync(bytes,0,bytes.Length);
                // 发送成功后会从这里继续开始执行
                // 失败时会抛出异常并在下面的catch块中被捕捉
                Console.WriteLine("发送完成");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                client.Close();
            }
        }
    }

前面的示例异步读取字符串中的行。 读取每一行后,代码将枚举字符串中的每个单词。 调用方将使用 await foreach 语句枚举每个单词。 当需要从源字符串异步读取下一行时,该方法将等待。

取消任务

在.Net和C#中运行异步代码相当简单,但是我们有时候需要取消正在进行的异步操作,这时我们可以通过CancellationToken来取消任务。

1. 可重复执行的耗时方法。示例:

Task _task = null;
CancellationTokenSource _cts = null;

private void Refresh()
{
  if(this._task != null && !this._task.IsCompleted)
    this._cts.Cancel();
  //
  this._cts = new CancellationTokenSource();
  this._task = Task.Run(() => {
    if(this._cts.IsCancellationRequested)
      return;
    
    //do something
  }, this._cts.Token);
}

2. 生命周期结束取消未完成任务。示例:

//本地变量
Task _task = null;
CancellationTokenSource _cts = null;

//初始化时,开启倒计时任务
private void Init()
{
  this._cts = new CancellationTokenSource();
  this._task = Task.Run(() => {
    if(this._cts.IsCancellationRequested)
      return;

    //do something
  },  this._cts.Token)
}

//退出时,取消未完成任务
private void Exit()
{
  if(this._task != null && !this._task.IsCompleted)
    this._cts.Cancel();
}

参考:Microsoft C# 异步编程

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/976041.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号