首页 > 编程语言 >.NET异步编程模式(二)

.NET异步编程模式(二)

时间:2022-08-15 10:37:54浏览次数:53  
标签:异步 编程 var ThreadID IAsyncResult new NET public

在 C#1 的时候就包含了APM,在 APM 模型中,异步操作通过 IAsyncResult 接口实现,包括两个方法 BeginOperationName 和 EndOperationName ,分别表示开始和结束异步操作。

Demo

我们先来看一个同步示例。新建WPF程序,在界面上放一个按钮。点击按钮访问外网,会有一定时间的阻塞。

private void SyncBtn_Click(object sender, RoutedEventArgs e)
{
    // 记录时间
    Debug.WriteLine(DateTime.Now.TimeOfDay.ToString() + 
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);

    // 访问外网网站网站
    var req = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
    req.GetResponse();

    // 记录时间
    Debug.WriteLine(DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}

当我们点击按钮后,因为web请求是同步的,会阻塞UI线程一定时间。从输出日志上看阻塞时间是 1 秒钟左右,此时界面呈卡死状态。

日志输出如下:

13:16:09.5031834,ThreadID = 1

13:16:10.5220362,ThreadID = 1

从运行效果和日志,我们可以看出:

  • WebRequest方法调用前后都是在同一个线程上执行-UI线程
  • WebReqeust方法阻塞了UI线程,导致“假死”现象

WebRequest也提供了异步方法,BeginGetResponse,EndGetResponse。我们修改一下代码,新增一个按钮。

private void APM_Btn_Click(object sender, RoutedEventArgs e)
{
    // 记录时间
    Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);

    // 访问外网网站网站
    var req = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
    req.BeginGetResponse(new AsyncCallback(t => { WebRequestCallback(t,req); }), null);

    // 记录时间
    Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}

/// <summary>
/// 异步回调
/// </summary>
/// <param name="result"></param>
private void WebRequestCallback(IAsyncResult result, WebRequest request)
{
    var response = request.EndGetResponse(result);
    // 获取返回数据流
    var stream = response.GetResponseStream();

    using(StreamReader reader = new StreamReader(stream))
    {
        StringBuilder sb = new StringBuilder();
        while(!reader.EndOfStream)
        {
            var content = reader.ReadLine();
            sb.Append(content);
        }

        // 记录时间
        Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +
                        ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
    }
}

运行效果如下:

日志输出如下:

1-13:10:01.7734197,ThreadID = 1

3-13:10:01.8826176,ThreadID = 1

2-13:10:03.2614022,ThreadID = 14

从运行效果和日志,我们可以看出:

  • 异步方法不会阻塞调用方法,调用后立刻返回
  • 异步方法会在另外一个线程上执行

IAsyncResult

BeginOperationName 方法会返回一个实现了 IAsyncResult 接口的对象。该对象存储了关于异步操作的信息。

转到定义,我们可以看到接口中都包含哪些内容:

自定义异步方法

实现该接口,定义自己的异步方法。

public class MyWebRequestResult : IAsyncResult
{
    /// <summary>
    /// 用户定义属性,可以存放数据
    /// </summary>
    public object? AsyncState => throw new NotImplementedException();
    /// <summary>
    /// 获取用于等待异步操作完成的 WaitHandle
    /// </summary>
    public WaitHandle AsyncWaitHandle => throw new NotImplementedException();
    /// <summary>
    /// 表示异步操作是否是同步完成
    /// </summary>
    public bool CompletedSynchronously => throw new NotImplementedException();
    /// <summary>
    /// 表示异步操作是否完成
    /// </summary>
    public bool IsCompleted => throw new NotImplementedException();
}

我们需要新建一个回调函数:

public class MyWebRequestResult : IAsyncResult
{
    /// <summary>
    /// 异步回调函数
    /// </summary>
    private AsyncCallback _callback;
    public string Result { get; private set; }
    // 构造函数
    public MyWebRequest(AsyncCallback asyncCallback, object state)
    {
        _callback = asyncCallback;
    }
    // 设置结果
    public void SetComplete()
    {
        AsyncState = result;
        Result = result;
        if(null != _callback)
        {
            _callback(this);
        }
    }
    // ...
}

在次之后就可以自定义 APM 异步模型了:

public IAsyncResult BeginMyWebRequest(AsyncCallback callback)
{
    // 1. 先给 IAsyncResult 进行赋值
    var myResult = new MyWebRequestResult(callback, null);
    var request = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
    // 2. 新建线程,执行耗时任务
    new Thread(() => {
        using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream()))
        {
            var str = sr.ReadToEnd();
            // 3. 耗时任务结束后 调用回调函数 & 保存结果
            myResult.SetComplete(str);
        }
    }).Start();

    return myResult;
}

public string EndMyWebRequest(IAsyncResult asyncResult)
{
    MyWebRequestResult myResult = asyncResult as MyWebRequestResult;
    return myResult.Result;
}

新增一个按钮,进行调用:

private void MyAPM_Btn_Click(object sender, RoutedEventArgs e)
{
    // 记录时间
    Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);

    // 调用 Begin 方法
    BeginMyWebRequest(new AsyncCallback(MyAPM_Callback));

    // 记录时间
    Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}

private void MyAPM_Callback(IAsyncResult result)
{
    // 从这里可以获得 异步操作的结果
    var myResult = result as MyWebRequestResult;
    var msg = EndMyWebRequest(myResult);

    // 记录时间
    Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +
                    ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}

运行效果如下:

日志输出如下:

1-14:48:42.7278184,ThreadID = 1

3-14:48:42.7311174,ThreadID = 1

2-14:48:45.1049069,ThreadID = 6

结合效果和日志,我们可以得出如下结论:

  • 自定义的异步方法没有导致 UI 卡顿
  • APM就是把耗时的任务交给新线程去做,然后利用委托进行回调

普通方法的异步

如果是普通方法,也可以通过 委托异步(BeginInvoke, EndInvoke):

public void MyAction()
{
    var func = new Func<string, string>(t => {
        Thread.Sleep(2000);
        return t;
    });

    func.BeginInvoke("inputStr", t => {
        string result = func.EndInvoke(t);
    },null);
}

总结

  1. APM 模型是基于IAsyncResult来实现异步操作的
  2. 异步操作开始时,把委托传递给 IAsyncResult
  3. 在新线程上执行耗时操作
  4. 耗时操作结束后,修改 IAsyncResult 里的结果数据,并调用 IAsyncResult 里的委托回调
  5. 在回调里获取 异步操作 的结果

标签:异步,编程,var,ThreadID,IAsyncResult,new,NET,public
From: https://www.cnblogs.com/jqwang/p/16587349.html

相关文章

  • .net6 健康检查
    publicvoidConfigureServices(IServiceCollectionservices){services.AddControllersWithViews().Services......
  • .NET异步编程模式(三)
    EAP(Event-basedAsynchronousPattern)是基于事件的异步模式,在.NETFramework2.0中引入。EAP需要一个有Async后缀方法和一个或多个事件。EAP不再推荐用于新开发。......
  • .NET 7 发布的最后一个预览版Preview 7, 下个月发布RC
    微软在2022年8月9日发布了.NET7Preview7[1],这是它在11月10日RTM之前进入发布候选阶段之前的最后预览版。预览版7已在VisualStudio17.4预览版1中进行了测试,......
  • .net Web 项目的文件/文件夹上传下载
    ​需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制。 第一步:......
  • 在.NET 6.0中使用不同的托管模型
    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。本章是《定制ASPNET6.0框架系列文章》的第六篇。在......
  • shell编程之免交互
    Shell编程之免交互一、HereDocument免交互1.HereDocument概述HereDocument使用I/O重定向的方式将命令列表提供给交互式程序或命令,比如ftp、cat或read命令。HereDo......
  • 服务端高性能网络IO编程模型简析
    服务端高性能网络IO编程模型简析一、客户端与服务器端多数网络应用可以分为客户端(client)和服务器端(server)模型,然后中间通过各种定义的协议来进行两端的通信。比如......
  • Netty - ByteBuffer
     一、ByteBuffer使用    二、ByteBuffer结构      三、ByteBuffer常见API3.1分配空间allocateV.S.allocateDirect ......
  • dotnet build error CS5001: Program does not contain a static 'Main' method suita
    前言Docker环境编译.Net6项目,出现诡异的CS5001Programdoesnotcontainastatic'Main'methodsuitableforanentrypoint排查从报错信息看是Program.csMain方......
  • 基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片
    系列文章基于.NetCore开发博客项目StarBlog-(1)为什么需要自己写一个博客?基于.NetCore开发博客项目StarBlog-(2)环境准备和创建项目基于.NetCore开发博客项目......