在.Net中使用异步

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

在写程序的过程中,我们可能会需要对某些功能实现异步操作,比如记录调用日志等。

提到异步,我们最容易想到的就是多线程:我们可以启动另外一个线程,把一部分工作交给另外一个线程去执行,而当前线程继续去做一些更加急迫的事情。这里的“把一部分工作交给另外一个线程取执行”,是通过将要执行的函数的函数入口地址告诉另外一个线程来实现的,当新的线程有了函数的入口地址,就可以调用该函数。

我们先来看一下怎样使用C#中的Thread类来实现异步。

  • 使用Thread类异步执行一个方法

在C#中,Thread类是常用的用来启动线程的类:

        static void Main(string[] args)
        
{
            Thread thread 
= new Thread(new ThreadStart(myStartingMethod));
            thread.Start();
        }


        
static void myStartingMethod()
        
{
        }

实际上,这里创建的ThreadStart对象,封装的就是方法“myStartingMethod”的入口地址。C#中通过Delegate对象,可以方便的封装函数入口地址。

而Delegate,实际上是用来描述函数定义的,比如上面提到的ThreadStart委托,他的声明如下:

public delegate void ThreadStart();

这句话声明了一个叫做ThreadStart的委托类型,而且该声明表示:ThredStart这个委托类型,只能封装“返回值为void、没有参数”的函数的入口地址。如果我们给ThreadStart类的构造函数传递的方法不符合,则会出错:

 

        static void Main(string[] args)
        
{
            
// 错误 “myStartingMethod”的重载均与委托“System.Threading.ThreadStart”不匹配
            Thread thread = new Thread(new ThreadStart(myStartingMethod));
            thread.Start();
        }


        
static void myStartingMethod(int a)
        
{
        }

实际上,我们在使用多线程时,要异步执行的函数往往会有一些参数。比如记录日志时,我们需要告诉另外一个线程日志的信息。

  • 异步执行一个带参数的方法

    因此,Thread类除了接受ThreadStart委托,还接受另外一个带参数的委托类型ParameterizedThreadStart:

            static void Main(string[] args)
            
    {
                Thread thread 
    = new Thread(new ParameterizedThreadStart(myStartingMethod));
                thread.Start(null);
            }


            
    static void myStartingMethod(object threadData)
            
    {
                
    // do something.
            }

    ParameterizedThreadStart 委托可以用来封装返回值为void、具有一个object类型参数的函数。这样,我们就可以往另外一个函数中传递参数了——只不过,如果要传递多个参数,我们必须将参数封装一下,弄到一个object对象中去。比如下面的例子中,本来我们需要传递两个整数的,但为了符合ParameterizedThreadStart的声明,我们需要改造一下函数:

            static void Main(string[] args)
            
    {
                Thread thread 
    = new Thread(new ParameterizedThreadStart(myStartingMethod));
                MyStartingMethodParameterWarpper param 
    = new MyStartingMethodParameterWarpper();
                param.X 
    = 1;
                param.Y 
    = 2;
                thread.Start(param);
            }


            
    static void myStartingMethod(object threadData)
            
    {
                MyStartingMethodParameterWarpper param 
    = (MyStartingMethodParameterWarpper)threadData;
                
    int value = param.X + param.Y;
                
    // do something
            }


            
    public class MyStartingMethodParameterWarpper
            
    {
                
    public int X;
                
    public int Y;
            }

    ParameterizedThreadStart委托必须与Thread.Start(Object) 方法一起使用——委托只是用来传递函数入口,但函数的参数是通过Thread.Start方法传递的。

    另外需要注意的,从这里我们可以看到,这样的使用方法并不是类型安全的,我们无法保证myStartingMethod方法的参数threadData永远都是MyStartingMethodParameterWarpper 类型,因此我们还需要加上判断;另外这样实际上也加大了程序间的沟通成本:如果有人需要异步执行myStartingMethod方法,那么他就必须知道其参数的实际类型并保证参数传递正确,而这块编译器已经无法通过编译错误的方式通知你了。

  • 怎样获得异步执行的结果?

    至此,我们只解决了传递参数的问题。

    Thread类无法执行一个包含有返回值的函数。我们知道“int a = Math.Sum(1, 2)”是将Sum函数的返回结果复制给了变量a,但如果用了多线程,那么这个线程不知道将这个返回结果复制到哪里,因此接受这样的一个函数是没有意义的。于是产生了另外一个重要的问题:如果我想要知道一步执行的结果,也就是如果我的线程函数具有返回值,我应该怎样做呢?

    解决的方法有很多种。

    顺着刚才解决传递参数的思路,我们可能会想到:如果Thread类接受一个包含有一个object类型的输入参数和一个object类型的输出参数,不就可以了么?嗯,这个思路听起来不错。不过很不幸的是,MS并没有提供这个接口。

    如此看来,我们是没法直接得到异步函数的执行结果了。

    不过没关系,我们可以间接的得到——我们可以在线程函数内,把函数的返回值保存在一个约定好的地方,然后在主线程到那里去取就可以了!

    因此,考虑到object对象是引用类型,我们可以返回值直接放在线程函数的参数中:

     

            static void Main(string[] args)
            
    {
                Thread thread 
    = new Thread(new ParameterizedThreadStart(myStartingMethod));
                MyStartingMethodParameterWarpper param 
    = new MyStartingMethodParameterWarpper();
                param.X 
    = 1;
                param.Y 
    = 2;
                thread.Start(param);
                
    while(thread.ThreadState != ThreadState.Stopped)
                
    {
                    Thread.Sleep(
    10);
                }

                Console.WriteLine(param.Value);
            }


            
    static void myStartingMethod(object threadData)
            
    {
                MyStartingMethodParameterWarpper param 
    = (MyStartingMethodParameterWarpper)threadData;
                param.Value 
    = param.X + param.Y;
            }


            
    public class MyStartingMethodParameterWarpper
            
    {
                
    public int X;
                
    public int Y;
                
    public int Value;
            }

     

    回顾上面的封装函数参数、封装函数返回值的做法,我们的思路实际上是“将线程函数的参数、返回值封装在对象中”。而刚刚我们也提到了,ParameterizedThreadStart 委托和 Thread.Start(Object) 方法重载使得将数据传递给线程过程变得简单,但由于可以将任何对象传递给 Thread.Start(Object),因此这种方法并不是类型安全的。将数据传递给线程过程的一个更可靠的方法是将线程过程和数据字段都放入辅助对象:

     

            static void Main(string[] args)
            
    {
                MyClass obj 
    = new MyClass();
                obj.X 
    = 1;
                obj.Y 
    = 2;
                Thread thread 
    = new Thread(new ThreadStart(obj.myStartingMethod));
                thread.Start();
                
    while(thread.ThreadState != ThreadState.Stopped)
                
    {
                    Thread.Sleep(
    10);
                }

                Console.WriteLine(obj.Value);
            }


            
    public class MyClass
            
    {
                
    public int X;
                
    public int Y;
                
    public int Value;

                
    public void myStartingMethod()
                
    {
                    
    this.Value = this.X + this.Y;
                }

            }

     

  • 怎样知道线程函数已经执行完毕

    刚才在我们获取函数返回值时,都使用了一个While循环来等待线程函数执行完毕。但这种方式可能是不好的——假设我们启动一个线程,这个线程尝试去获得一个打开的数据库链接,而主程序需要在获得该连接后马上得到通知。看下面这段:

            static void Main(string[] args)
            
    {
                MyClass obj 
    = new MyClass();
                Thread thread 
    = new Thread(new ThreadStart(obj.MyStartingMethod));
                thread.Start();

                
    // 
                if(!SomethingDone && thread.ThreadState == ThreadState.Stopped)
                
    {
                    DoSomething();
                }


                
    // 事情1
                
    // .....
                if(!SomethingDone && thread.ThreadState == ThreadState.Stopped)
                
    {
                    DoSomething();
                }


                
    // 事情2
                
    // .....
                if(!SomethingDone && thread.ThreadState == ThreadState.Stopped)
                
    {
                    DoSomething();
                }


                
    // 事情3
                
    // .....
                if(!SomethingDone && thread.ThreadState == ThreadState.Stopped)
                
    {
                    DoSomething();
                }


                
    // 事情4
                
    // .....
                if(!SomethingDone && thread.ThreadState == ThreadState.Stopped)
                
    {
                    DoSomething();
                }


                
    // ......
            }


            
    static bool SomethingDone = false;
            
    static void DoSomething()
            
    {
                SomethingDone 
    = true;
                
    // do something
            }


            
    public class MyClass
            
    {
                
    public OdbcConnection OpenConnection;

                
    public void MyStartingMethod()
                
    {
                    
    this.OpenConnection = new OdbcConnection();
                    
    // do something
                    this.OpenConnection.Open();
                }

            }

    上面的代码,虽然我们在每次执行一个代码段后就判断线程有没有执行完,但实际上仍然不是及时的——仍然无法保证在函数执行完后就第一时间就启动了函数DoSomething,因为每个代码段执行过程中也许消耗了很长时间,而在这段时间内另一个线程早就执行完了。

    这样的主动轮询的方法,实在是比较累,而且及时性也不好。

    那么,Thread类接受了一个函数入口地址,线程在启动后就会去执行这个函数。那么,假设我们给线程多传递一个函数入口地址,叫线程在执行完线程函数之后就马上执行这个函数,那我们岂不是。。。就能第一时间得知函数已经执行完了?想法很好。看我们来改造:

            static void Main(string[] args)
            
    {
                MyClass obj 
    = new MyClass();
                obj.X 
    = 1;
                obj.Y 
    = 2;
                obj.OnMyStartingMethodCompleted 
    = new MyStartingMethodCompleteCallback(WriteResult);
                Thread thread 
    = new Thread(new ThreadStart(obj.MyStartingMethod));
                thread.Start();

                
    // wait for process exit...
            }


            
    static void WriteResult(MyClass sender)
            
    {
                Console.WriteLine(sender.Value);
            }


            
    public delegate void MyStartingMethodCompleteCallback(MyClass sender);

            
    public class MyClass
            
    {
                
    public int X;
                
    public int Y;
                
    public int Value;
                
    public MyStartingMethodCompleteCallback OnMyStartingMethodCompleted;

                
    public void MyStartingMethod()
                
    {
                    
    this.Value = this.X + this.Y;

                    
    // 函数已经执行完了,调用另外一个函数。
                    this.OnMyStartingMethodCompleted.Invoke(this);
                }

            }

    注意线程方法MyStartingMethod的最后一句,这里实际上就是执行了委托对象OnMyStartingMethodCompleted中所封装的那个函数入。当然为此我们专门定义了一个表示方法MyStartingMethod已经执行完毕的一个委托MyStartingMethodCompleteCallback,他没有返回值,只有一个参数就是方法MyStartingMethod所属的对象。

    当然,这里的通知,我们也可以使用Event来实现

 
掌握C#异步编程:ASP.NET Core中的异步方法使用 本文深入探讨了在ASP.NET Core应用程序中如何有效地使用异步方法,包括对异步特性的理解、async和await关键字的运用,以及如何处理异步可枚举对象。通过具体代码示例,展示了异步编程如何提升应用程序性能,特别是利用.NET Core支持简化异步方法的语法。 阅读详情

相关推荐

基于ASP.NET接口的AJAX异步请求实例(不是控件)

通过实现ICallbackEventHandler接口来再请求本页的代码页面,可以用在数据的操作,后台代码可以随便写。 还有一个是最基本的AJAX入门实例,基于url请求的

【转】.Net中的异步编程总结

一直以来很想梳理下我在开发过程中使用异步编程的心得和体会,但是由于我是APM异步编程模式的死忠,当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式,所以导致我一直没有花太多心思去整理这两部分异步编程模型。今天在CodeProject上面偶然间看到一篇关于异步编程的文章,概括总结的非常好,省去了我自己写的麻烦,索性翻译过来,以飨各位。 在阻塞和并行编程过程中,异步多线程是非常重要的特性。异步编程可以涉及到多线程,也可以不用涉及。如果我们能够把二者放到一起来讲解的话,我们会理解的更快...

sinolover的专栏 1324

.Net异步编程

.Net异步编程历史异步模式功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 历史 现在比较流行的异步编程应该是Task模式,这是基于Threadpool线程池实现的更加方便简单的开发模式,但是为了更好理解它的原理,有必要先了解

CSharpIsDog的博客 719

多种实现异步编程的方法

2️⃣ Future件彼此孤立:有时某一个耗时很长的异步任务执行结束之后,你想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future都是彼此之间都是孤立的,所以才有了后面的CompletableFuture,CompletableFuture就可以将多个Future串联起来形成任务流。我们知道Future表示一个异步计算任务,当任务完成时可以得到计算结果。

weixin_43702295的博客 1112

.NET&C#异步编程

在设计异步编程时,要确定异步操作是I/O-Bound(因I/O阻塞,又称为I/O密集型),还是CPU-Bound(因CPU阻塞,又称为计算密集型),从而更好的选择方式方法。计算密集型并不是任务越多越好,如果任务数量超过CPU的核心数,那么花费在任务切换上的时间就越多,CPU的执行效率就越低。I/O密集型由于任务主要在硬盘读写和网络读写上,所以CPU就可以处理非常多的任务。之所以有这篇文章,因为没有搜到类似本文,仅需一篇文章记录尽量全面的文章,所以就做了回搬运工,整理汇总一下。

jkinbor的博客 236

.NET多线程异步调用的小结

最近看了看.NET异步调用方面的资料,现择重点总结,若有纰漏敬请指正。异步调用的实质:异步调用通过委托将所需调用的方法置于一个新线程上运行,从而能够使一个可能需要较长时间的任务在后台执行而不影响调用方的其他行为。异步调用的实现:前面已经说道,异步调用通过委托实现。委托支持同步和异步调用。在同步调用中,一个委托的实例可记录多个目标方法;在异步调用中,一个委托实例中有且只能包含一个目标方法。异步调用使...

weixin_30275415的博客 184

使用.NET异步委托

异步委托通过编译器提供的BeginInvoke方法与EndInvoke方法对delegate进行异步的调用.被调用方法将在系统线程池中运行,并且被调用方没有必要为异步执行行为提供附加的代码支持.异步执行结束时,通过调用回调函数发出通知.如果委托在异步执行中抛出了异常,那么在调用EndInvoke时才会被捕获到MSDN页面:ms-help://MS.VSCC.2003/MS.MSDNQTR

qunta的专栏 1872

如何在ASP.NET页面中使用异步任务(PageAsyncTask)

在页面加载期间,可能有些操作是要比较耗用时间的(例如调用外部资源,要长时间等待其返回),正常情况下,这个操作将一直占用线程。而大家知道,ASP.NET在服务端线程池中的线程数是有限的,如果一直占用的话,就会导致其他操作需要等待。 在ASP.NET 2.0中,提供了一种异步页的技术。微软有一个专门的文档介绍这个技术 看看下面这个图,很重要 【注意】ASP.NET异步机制与windows Fro

陈希章@中国 1094

ASP.NET MVC什么时候使用异步Action

在没有使用异步Action之前,在Action内,比如有如下的写法: public ActionResult Index() { CustomerHelper cHelper = new CustomerHelper(); List<Customer> result = cHelper.GetCustomerData(); retu...

weixin_30662011的博客 264

asp.net html5 异步,在 ASP.NET 4.5 中使用异步方法

在 ASP.NET 4.5 中使用异步方法01/02/2019本文内容.Net 4.5中的 ASP.NET 4.5 网页使你能够注册返回类型为Task的对象的异步方法。 .NET Framework 4 引入了一个称为 任务 的异步编程概念,ASP.NET 4.5 支持 任务。 任务由tasks 命名空间中的任务类型和相关类型来表示。 .NET Framework 4.5 使用 await 和 a...

weixin_39821189的博客 511

Net-Snmp多线程以及异步使用

最近使用到了Net-Snmp来做一个设备信息查询的manager,因为在使用的时候查询的设备以及需要的oid比较多,使用同步的方式一个一个查询会使查询速度慢,所以在这里有两种解决方式。 多线程 因为刚开始使用Net-snmp不是很熟悉,所以立马想到的就是使用多个线程提高并发。 关于多线程的API,可以在Net-SNMP的rREADME文档看到如下内容: Traditional...

CC_fighting的博客 2626

使用ASP.NET MVC Futures 中的异步Action

之前看过老赵这两篇文章。也研究了一下老赵异步的实现方式。<br /><br /> 不过感觉自己扩展的话,在ASP.NET MVC中使用异步还真是麻烦,刚好看到从RC1版开始 ASP.NET MVC Futures中提供了几个支持异步的类。<br /><br /> 相关的类包括 AsyncActionDescriptor、AsyncController、 AsyncControllerActionInvoker、AsyncManager、AsyncResultWrapper、 AsyncTi

615

dotNetFlexGrid-asp.net异步表格控件 使用指南

dotNetFlexGrid-asp.net异步表格控件 使用指南 author:hualei created by:2010-08-1719:36 saved by:2010-08-1721:35   修订历史 日期 人员 修订说明 2012-03-14 华磊 Created.

yddongzs的专栏 1749

asp.net异步执行方法_在ASP.NET 4.5中使用异步方法的魔力以及重要的陷阱

asp.net异步执行方法First, I encourage you to listen to episode 327 of the Hanselminutes podcast. We called it "Everything .NET programmers know about Asynchronous Programming is wrong" and I learned a lot. ...

cunfuxiao7305的博客 467

ASP.NET MVC中使用ASP.NET AJAX异步访问WebService

使用过ASP.NET AJAX的朋友都知道,怎么通过ASP.NET AJAX在客户端访问WebService,其实在ASP.NET MVC中使用ASP.NET AJAX异步访问WebService 也没什么大的差别. 在ASP.NET应用程序里使用ASP.NET AJAX访问WebService通常都是通过ScriptMananger引入WebService生成客户端代理的方法,同时也...

aozhilu9748的博客 188

asp.net mvc 特性 使用异步Controllers

使用asp.net异步执行请求的原因和好处,可以搜之,不赘叙。 看一个同步抓取网页图片的方法: Public ContentResult GetPhotoByTag(string tag) { // Make a request to Flickr string url = string.Format(FlickrSearchApi, HttpUtility.UrlEn...

weixin_30575309的博客 170
上一篇: C#中的@符号
下一篇: c#判断文件是否为图片
f_c_sh
博客等级 码龄20年 26粉丝 53原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值