C# 异步

一、异步概述

  先放上MSDN链接
  关于异步一些关键点:

  1. 异步通常适用于需要I/O操作(包括请求网络数据等)以及需要CPU进行大量计算的场景
  2. C# 的异步是利用task和Task实现的。
  3. async关键字会将普通方法变成异步方法,并允许在其方法体中使用await关键字
  4. 应用 await 关键字后,它将挂起调用方法,并将控制权返还给调用方,直到等待的任务完成。
  5. 仅允许在异步方法中使用 await。

  编译器将C#代码转换为状态机,它将跟踪类似以下内容:到达 await 时暂停执行以及后台作业完成时继续执行。(ps:async方法中如果没有await的话方法将不具有异步返回的特性,而是以同步的方式执行)

  需要注意的是:以非阻塞方式等待任务结束阻塞当前线程以等待task结束会导致死锁,并且阻塞的上下文线程并且可能需要更复杂的错误处理。可以使用异步的方法来代替:
在这里插入图片描述

二、task与task <T>

  task是用于实现称之为并发 Promise 模型的构造。task是在操作层面上的抽象而不是线程上的抽象。默认情况下,任务在当前线程上执行,且在适当时会将工作委托给操作系统。 可选择性地通过 Task.Run API 显式请求任务在独立线程上运行。(实际上task是基于threadpool实现的,被标记为LongRunning的Task则会单独创建线程实现)

MSDN举的例子:
I/O:

class DotNetFoundationClient
{
    // HttpClient is intended to be instantiated once per application, rather than per-use.
    private static readonly HttpClient s_client = new HttpClient();

    public Task<string> GetHtmlAsync()
    {
        // Execution is synchronous here
        var uri = new Uri("https://www.dotnetfoundation.org");

        return s_client.GetStringAsync(uri);
    }

    public async Task<string> GetFirstCharactersCountAsync(int count)
    {
        // Execution is synchronous here
        var uri = new Uri("https://www.dotnetfoundation.org");

        // Execution of GetFirstCharactersCountAsync() is yielded to the caller here
        // GetStringAsync returns a Task<string>, which is *awaited*
        var page = await s_client.GetStringAsync(uri);

        // Execution resumes when the client.GetStringAsync task completes,
        // becoming synchronous again.

        if (count > page.Length)
        {
            return page;
        }
        else
        {
            return page.Substring(0, count);
        }
    }
}

  在上述第二个示例方法 GetFirstCharactersCountAsync() 中,Task 对象直接从 GetStringAsync 返回。 由于使用了 await 关键字,因此该方法会返回一个新建的任务对象。 在 GetFirstCharactersCountAsync 方法中,控制权从此位置返回给调用方。 Task 对象的方法和属性使调用者能够监视任务的进度。GetFirstCharactersCountAsync 中剩余的代码执行完毕时,该任务便完成。

CPU:

public async Task<int> CalculateResult(InputData data)
{
    // This queues up the work on the threadpool.
    var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

    // Note that at this point, you can do some other work concurrently,
    // as CalculateResult() is still executing!

    // Execution of CalculateResult is yielded here!
    var result = await expensiveResultTask;

    return result;
}

  CalculateResult() 在调用它的线程上执行。 调用 Task.Run 时,它会在线程池上对要进行大量计算的DoExpensiveCalculation() 进行排队,并收到一个 Task 句柄。 DoExpensiveCalculation() 最终在下一个可用线程上并行运行(很可能在另一个 CPU 内核上)。一旦遇到 await,CalculateResult() 执行会让步于调用方,在 DoExpensiveCalculation() 执行运算的同时,允许其他任务在当前线程执行。 DoExpensiveCalculation() 完成后,结果会在主线程上排队等待运行。 最后,主线程将返回执行得到 DoExpensiveCalculation() 结果的 CalculateResult()。

假设有Main方法调用CalculateResult()

void Main()
{
    CalculateResult();
    dosomething...
}

  那么Main方法的执行流程图应该是这样的:

调用
可能在另一个线程上调用
返回Main方法执行
DoExpensiveCalculation执行结束
Main
CalculateResult
DoExpensiveCalculation
await
dosomething
end

Task的异常

  当异步运行的代码引发异常时,该异常存储在 Task 中。 Task.Exception 属性为 System.AggregateException,因为异步工作期间可能会引发多个异常。 引发的任何异常都将添加到 AggregateException.InnerExceptions 集合中。 如果该 Exception 属性为 NULL,则将创建一个新的 AggregateException 且引发的异常是该集合中的第一项。
  对于出错的任务,最常见的情况是 Exception 属性只包含一个异常。 当代码 await 出错的任务时,将重新引发 AggregateException.InnerExceptions 集合中的第一个异常。 因此,此示例的输出显示 InvalidOperationException 而不是 AggregateException。 提取第一个内部异常使得使用异步方法与使用其对应的同步方法尽可能相似。 当你的场景可能生成多个异常时,可在代码中检查 Exception 属性。

  由于前些天的死锁问题,看了下异步(async、await)和Task/Task<T>相关的内容,上面这些是看了MSDN以后摘要出来的,也算对异步做了个简单的梳理。还看到了一些相关的内容,比如TAP(The Task-Based Asynchronous Pattern)、Promise模式等,但是都比较抽象,还没弄懂。(补:看了些例子才发现所谓的TAP就是日常所用的异步操作。。。另外,又看到一篇讲的比较浅显易懂的文章,附在这里。以及一篇没太看懂的,只是大概理解了死锁的发生与线程池有些关系)但是感觉这么查资料理解的也不是很系统和深入,觉得还是应该去看下C#的书。

PS:在理解await时,突然迷惑于挂起和阻塞,才发现以前没有细想过它们的区别。结果查起来发现在C#中Thread.Suspend已经被弃用了,去找别的资料,发现好多人都不区分挂起和阻塞。唯一的共识就是:挂起是主动行为,也需要主动恢复;阻塞则是被动行为,是因为缺少资源导致不能继续运行,当所需资源被满足后则会自动继续运行。

  • 8
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中的异步TCPClient是一种用于进行网络通信的类。它允许你在进行网络连接、发送和接收数据时,使用异步操作来提高应用程序的性能和响应性。 要使用异步TCPClient,你需要使用C#异步编程模型(Async/Await)。以下是使用异步TCPClient的一些基本步骤: 1. 创建一个TCPClient对象,并指定要连接的远程主机和端口号。 2. 使用TCPClient对象的ConnectAsync方法,以异步方式连接到远程主机。 3. 一旦连接成功,你可以使用TCPClient对象的GetStream方法获取与远程主机进行通信的网络流。 4. 对于发送数据,你可以使用网络流的WriteAsync方法以异步方式发送字节数据或字符串。 5. 对于接收数据,你可以使用网络流的ReadAsync方法以异步方式接收字节数据或字符串。 下面是一个简单的示例代码,展示了如何使用异步TCPClient发送和接收数据: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; public class AsyncTcpClient { public static async Task Main() { // 远程主机的IP地址和端口号 string ipAddress = "127.0.0.1"; int port = 12345; // 创建TCPClient对象并连接到远程主机 TcpClient client = new TcpClient(); await client.ConnectAsync(IPAddress.Parse(ipAddress), port); // 获取与远程主机进行通信的网络流 NetworkStream stream = client.GetStream(); // 发送数据 string messageToSend = "Hello, server!"; byte[] sendData = Encoding.UTF8.GetBytes(messageToSend); await stream.WriteAsync(sendData, 0, sendData.Length); // 接收数据 byte[] receiveData = new byte
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值