.NET中的异步编制程序

#includenana/gui/wvl.hpp#includenana/gui/widgets/button.hpp#includenana/gui/widgets/progressbar.hpp#includenana/threads/pool.hppclassexample:publicnana::gui::form{public:example(){usingnamespacenana::gui;btn_start_.create(*this,10,10,100,20);btn_start_.caption(STR("Start"));btn_start_.make_eventevents::click(nana::threads::pool_push(pool_,*this,example::_m_start));btn_start_.make_eventevents::click(nana::threads::pool_push(pool_,*this,example::_m_ui_update));prog_.create(*this,10,40,280,20);prog_.style(false);this-make_eventevents::unload(*this,example::_m_cancel);}private:void_m_start(){btn_start_.enabled(false);nana::system::sleep(10000);//ablockingsimulationbtn_start_.enabled(true);}void_m_ui_update(){while(btn_start_.enabled()==false){prog_.inc();nana::system::sleep(100);}}void_m_cancel(constnana::gui::eventinfoei){if(false==btn_start_.enabled())ei.unload.cancel=true;}private:nana::gui::buttonbtn_start_;nana::gui::progressbarprog_;nana::threads::poolpool_;};intmain(){exampleex;ex.show();nana::gui::exec();return0;}

原稿地址:MultiThreading Using a Background Worker, C#

假若职务中现身了这几个,那么十分会被侵吞掉,并积存到贰个会集中去,而线程可以回来到线程池中去。可是假若在代码中调用了Wait方法仍然为Result属性,职务有十二分爆发就能被抓住,不会被私吞掉。个中Result属性内部本身也调用了Wati方法。Wait方法和上生龙活虎节中的委托的EndInvoke方法相符,会使得调用线程窒碍直到异步职责令功。上面我们会介绍如何制止获取异步结果的拥塞景况,在教师此前,先说一下,怎么着裁撤正在周转的职务。

本条大约的顺序模拟了二个耗时的操作,三个Start按键和七个Cancel开关分别表示运维和间断任务。简单想象,_m_start(State of Qatar会实践三个耗费时间的操作,当按下"Start"按键之后,会产生分界面的装死。那时单击“Cancel”也是未曾用的,因为对Start按键的事件管理还不曾截止,能做的只有等待。从图中能够观察,即使释放了鼠标,开关照旧按下的情况,因为单击事件还在管理中。日常,解决那类耗费时间题材正是将耗费时间的管理进程放置到三个独立的线程中,让UI线程有空暇的时间来响应顾客的操作,当耗费时间的操作达成,它就将结果回到给UI线程显示。构思上面包车型客车方案:

背景

在那个事例中,大家将使用Microsoft BackgroundWorker类,关于这么些类越来越多的新闻能够在这里.aspx)找到。

咱俩将创造多少个简易的推行耗费时间操作的应用程序,並且像顾客显示最终结果。耗费时间操作就要另三个线程中实行,並且在操作施行时期将不唯有以操作进度更新顾客分界面提醒音信。大家将允许顾客在此外时候裁撤操作。

请牢牢记住:唯有主线程本领访谈客商分界面,换句话说,你不可能在此外的线程中访谈客商控件。下边我们将详细讲明。

 

下边给出贰个例证来说学怎么着使用委托的来兑现异步调用函数。

_ui_03.png单击Start按键,程序会把_m_start()和_m_ui_update(卡塔尔(قطر‎都推入到线程池。是还是不是相当轻便?款待各位提出意见,提议,并展开研商。如果该贴已沉且有问号的意中人,能够去我的blog留言。

介绍

当开荒Windows Forms应用程序时,你会时时注意到:当履行有个别耗费时间的操作,比方拍卖贰个打文件恐怕从远程服务器央求数据 ,客商分界面会进来假死状态。这是由于你的应用程序是运作在单线程下。那么些线程担负响应顾客分界面包车型地铁操作,相同的时间也担负管理应用程序中具备的事件和方法。因而,耗费时间的操作会堵塞你的客商分界面,直到操作完结。后天,大家就要做的是把那个耗费时间的操作移到另贰个莫衷一是的线程中,当以在其他方面施行操作是,那样能够维持客户分界面通畅。

 

图片 1

图片 2

开创应用程序

笔者们就要Microsoft VS中创制贰个简约的Windows Forms应用程序,小编用的是Visual Studio 二〇〇八。像下图后生可畏律创立一个新的Windows Forms应用程序。小编更爱好使用C#,你也能够运用VB.NET.

图片 3

正如图生机勃勃律设置布局。作者个人合意用Table布局面板来协会自个儿的控件。那足以使空间保证不改变,当窗体放大或是调解大小的时候。大家要做的是加上三个TextBox(设置成Multiline格局)来体现职业线程的结果,二个NumbericUpAndDown允许大家炫证数字,叁个方始按键和多少个得了开关。

图片 4

 

在工具箱中,菜单和工具栏下,接纳加多一个StatusStrip。那使得大家能够拉长二个景况标签,大家就要标签上向顾客显示速度新闻。图片 5

在StatusStrip里,单击左下角的小箭头,选拔加多一个StatusLabel。重命名标签为lblStatus, 并且包它的Text属性设成空。

图片 6

在窗体的代码里,大家声澳优个门类为 BackgroundWorker的指标:

private BackgroundWorker myWorker = new BackgroundWorker();

在窗体的布局函数中,以上边包车型大巴质量起首化大家恰恰创设的worker:

下边是构造函数的造成代码:

public Form1()
        {
            InitializeComponent();

            myWorker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
            myWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
            myWorker.ProgressChanged += new ProgressChangedEventHandler(myWorker_ProgressChanged);
            myWorker.WorkerReportsProgress = true;
            myWorker.WorkerSupportsCancellation = true;
        }

近日我们来声称worker的事件管理函数:

上边大家创造多少个抽出一个卡尺头的助手方法,用这几个平头乘以1000,线程sleep250ms,然后回来结果。那是你的应用程序恐怕施行的耗费时间操作的一个模拟,你能够改动sleep的间距。注意,应为那几个方法是在DoWork事件里被调用的,这是后台线程会sleep给定的时光,并不是主线程。函数如下:

private int PerformHeavyOperation(int i)
{
    System.Threading.Thread.Sleep(250);
    return i * 1000;
}

转到设计分界面,双击Start按钮,转到它的事件管理函数中。我们要做的是从NumericUpAndDown控件中赢得数值,把那些数值传给异步线程,然后发轫实施background worker。大家需求在那处收获数字控件的值,因为若是我们到了新线程里,大家就不能访谈客商控件狼来了。为了起首background worker的实施,大家调用了RunWorkerAsync方法。那些主意接受二个object参数,那个参数将传给后台线程,在这里个object参数里,我们得以归入大肆五个控件的值。为了传入不仅二个值,大家运用object的数组。上面是btnStart_Click的实现代码。注意正在推行的worker,不能够被重复调用,你会拿走八个运维时不当假如你这么做的话。

private void btnStart_Click(object sender, EventArgs e)
{
    int numericValue = (int)numericUpDownMax.Value;//Capture the user input
    object[] arrObjects = new object[] { numericValue };//Declare the array of objects
    if (!myWorker.IsBusy)//Check if the worker is already in progress
    {
        btnStart.Enabled = false;//Disable the Start button
        myWorker.RunWorkerAsync(arrObjects);//Call the background worker
    }
}

现今在DoWork事件管理函数里,大家得以管理全部的耗费时间操作。首先,大家接纳从主线程中拿到到的目的,然后管理它们,最终把结果回到给主线程。记住独有主线程技能访谈顾客控件。当大家处理值的时候,大家会直接做两件事:

最终,大家把结果放到DoWork伊芙ntArgs参数的Result属性里,那将会被RunWorkerCompleted事件捕获。上边是DoWork的共同体代码:

protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker sendingWorker = 
    (BackgroundWorker)sender;//Capture the BackgroundWorker that fired the event
    object[] arrObjects = 
    (object[])e.Argument;//Collect the array of objects the we received from the main thread

    int maxValue = (int)arrObjects[0];//Get the numeric value 
            //from inside the objects array, don't forget to cast
    StringBuilder sb = new StringBuilder();//Declare a new string builder to store the result.

    for (int i = 1; i <= maxValue; i++)//Start a for loop
    {
        if (!sendingWorker.CancellationPending)//At each iteration of the loop, 
                    //check if there is a cancellation request pending 
        {
            sb.Append(string.Format("Counting number: {0}{1}", 
            PerformHeavyOperation(i), Environment.NewLine));//Append the result to the string builder
            sendingWorker.ReportProgress(i);//Report our progress to the main thread
        }
        else
        {
            e.Cancel = true;//If a cancellation request is pending, assign this flag a value of true
            break;// If a cancellation request is pending, break to exit the loop
        }
    }

    e.Result = sb.ToString();// Send our result to the main thread!
}

随之大家管理ProgressChanged事件。大家获取到调用ReportProgress方法时传出的整数值。注意你可以传任何项目标指标,通过应用ProgressChanged伊芙ntArgs参数的UserState属性。在这里你除了出示状态消息,还可应用进程条来突显当前行度。

protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //Show the progress to the user based on the input we got from the background worker
    lblStatus.Text = string.Format("Counting number: {0}...", e.ProgressPercentage);
}

进而是RunWorkerCompleted事件。我们率先检查worker是不是被裁撤,或是有别的不当发生。然后,大家大家获取background worker总结的结果,用于显示给顾客:

protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled && 
    e.Error == null)//Check if the worker has been canceled or if an error occurred
    {
        string result = (string)e.Result;//Get the result from the background thread
        txtResult.Text = result;//Display the result to the user
        lblStatus.Text = "Done";
    }
    else if (e.Cancelled)
    {
        lblStatus.Text = "User Canceled";
    }
    else
    {
        lblStatus.Text = "An error has occurred";
    }
    btnStart.Enabled = true;//Re enable the start button
}

还剩最终风华正茂件事,大家须求落到实处裁撤开关。双击cancel按键,踏向后台代码,调用 background worker的CancelAsync方法。那会把worker的CancellationPending标记设为true. 大家会在DoWork事件管理程序的轮回中检查这几个标识。至此,大家得以计算获得终止四个正在运营的backgroundworker并不会及时见到效果,若是background worker正在管理有些事,我们要求等待它达成技术废除操作。下边是btnCancel_Click的代码:

private void btnCancel_Click(object sender, EventArgs e)
{
    myWorker.CancelAsync();//Issue a cancellation request to stop the background worker
}

最终,运路程序的截图如下:

图片 7

操作结束的截图如下:

图片 8

 

#includenana/gui/wvl.hpp#includenana/gui/widgets/button.hpp#includenana/gui/widgets/progressbar.hppclassexample:publicnana::gui::form{public:example(){btn_start_.create(*this,10,10,100,20);btn_start_.caption(STR("Start"));btn_start_.make_eventnana::gui::events::click(*this,example::_m_start);btn_cancel_.create(*this,120,10,100,20);btn_cancel_.caption(STR("Cancel"));btn_cancel_.make_eventnana::gui::events::click(*this,example::_m_cancel);prog_.create(*this,10,40,280,20);}private:void_m_start(){working_=true;btn_start_.enabled(false);prog_.amount(100);for(inti=0;i100working_;++i){nana::system::sleep(1000);//along-runningsimulationprog_.value(i+1);}btn_start_.enabled(true);}void_m_cancel(){working_=false;}private:boolworking_;nana::gui::buttonbtn_start_;nana::gui::buttonbtn_cancel_;nana::gui::progressbarprog_;};intmain(){exampleex;ex.show();nana::gui::exec();return0;}

使用代码

作者将会一步步的显得应用程序中央银行使的代码,最终作者会附上源代码。

 

class Program
    {
        public delegate void DoWork();
        static void Main(string[] args)
        {
            DoWork d = new DoWork(WorkPro);//no.1

            d.BeginInvoke(null, null);//no.2
            for (int i = 0; i < 100; i++)//no.3
            {
                Thread.Sleep(10);//主线程需要做的事
            }
            Console.WriteLine("主线程done");
            Console.ReadKey();
        }
        public static void WorkPro()
        {
            //做一些耗时的工作
            Thread.Sleep(2000);
            Console.WriteLine("异步调用结束");
        }
    }
#includenana/gui/wvl.hpp#includenana/gui/widgets/button.hpp#includenana/gui/widgets/progressbar.hpp#includenana/threads/pool.hppclassexample:publicnana::gui::form{public:example(){btn_start_.create(*this,10,10,100,20);btn_start_.caption(STR("Start"));btn_start_.make_eventnana::gui::events::click(nana::threads::pool_push(pool_,*this,example::_m_start));btn_cancel_.create(*this,120,10,100,20);btn_cancel_.caption(STR("Cancel"));btn_cancel_.make_eventnana::gui::events::click(*this,example::_m_cancel);prog_.create(*this,10,40,280,20);this-make_eventnana::gui::events::unload(*this,example::_m_cancel);}private:void_m_start(){working_=true;btn_start_.enabled(false);prog_.amount(100);for(inti=0;i100working_;++i){nana::system::sleep(1000);//along-runningsimulationprog_.value(i+1);}btn_start_.enabled(true);}void_m_cancel(){working_=false;}private:volatileboolworking_;nana::gui::buttonbtn_start_;nana::gui::buttonbtn_cancel_;nana::gui::progressbarprog_;nana::threads::poolpool_;};intmain(){exampleex;ex.show();nana::gui::exec();return0;}

异步编制程序在前后相继设计中也是特别复杂的,稍有不慎,就能够使得你的应用程序变得不安宁,现身万分,以致会奔溃。可是,相比较幸运的是,.net提供极其方便的框架来進展异步编制程序,在自家看来.net中落实异步有三种方法,第风度翩翩种是八线程的主意,第三种是使用异步函数,其实在异步函数中接受的依旧十六线程的技巧。接下来就介绍在.net中什么运用多线程和异步函数来缓慢解决总计范围、耗费时间等那一个不友好客户体验的标题。

Nana库提供了一个线程池的类。清除那类难题时,使用线程池能够解脱对线程的军事管制,比如,创造,等待和销毁的难点。上边两段代码特别的相同,不过最要紧的分别是_m_start(卡塔尔(قطر‎被分派到线程池中并有线程池中,并有池中的线程管理,UI线程也就此不会被阻塞并有空闲的年华来响应顾客操作。这里有贰个叫pool_push(卡塔尔国的函数,它创制多个pool_pusher函数对象用于把_m_start(State of Qatar函数推入线程池,换句话说,就是将pool_pusher函数对象当作了事件处理,当点击Start开关时,pool_pusher会被调用,同一时候_m_start(卡塔尔则被推入到线程池中举办。在该版本中,form对象注册了多少个unload事件并调用_m_cancel(卡塔尔国,当关闭窗口的时候,程序放任了剩余的耗费时间操作。可是这里有一个主题材料需求应对,当耗费时间操作还在做事的时候,关闭窗口会招致按键和进度条也如出风流倜傥辙被衰亡,然而耗时操作并为结束,假若此刻耗费时间操作访谈了按键和进程条对象是否会引致程序崩溃?回答是会崩溃,不过上边的代码会防止在耗费时间操作实现以前销毁开关和进程条,在类中,线程池的靶子注明在按键和进程条之后,那表示线程池会在开关和进程条此前析构,当析构线程池的时候,它会等待全部的办事线程都曾经终结。在后台线程中拍卖窒碍操作在某个境况下,耗费时间专门的学业不会被撤除,也不精通当前的快慢。程序平日会用一直滚动的进度条表示正在处理。

先是来解释一下BeginInvoke艺术的第二个参数是AsyncCallBack 品种的委托(回调函数),当该参数不为空,那么在异步函数实践落成之后,会调用该信托;第多少个参数Object 体系的,代表传递给回调函数的异步调用状态。CallBack回调函数必得含有五个IAsyncResult 项指标参数,通过那个参数能够在回调方法内部获得异步调用的结果。在no.1出就给BeginInvoke函数字传送递了回调函数CallBack,和委托d,当异步数WorkPro推行实现之后,就立时通报CallBack回调函数来展示实行结果。那下主线程就不须求梗塞一向的等待异步函数的结果,大大的进步了程序的运维功效。在.net还提供不计其数类的BeinXXX()EndXXX()的异步版本,举个例子文件的读写等,具体可以查看有关的资料。

跌落二十多线程开辟的难度是NanaC++Library的规划目的。项目网页地址介绍贴绝大相当多事件回调函数都会急忙地奉行到位,并不会促成对界面包车型客车假死。Nana库的风浪模型是对事件队列的依次管理,那意味着当前叁个事件管理函数完毕之后才会调用下一个。构思下边包车型大巴例证:

如今就说过了,获取职务结果调用Wait方法和Result品质招致调用线程拥塞,那么如何管理这种意况呢,那就应用了Task<TResult>类提供的ContinueWith方式。该格局的职能是当职分完成时,运转贰个新的天职,不仅是如此,该措施还只怕有能够在职分只现身极度也许吊销等景况的时候才实践,只必要给该办法传递TaskContinuationOptions枚举类型就足以了。下边就演示一下什么样选用ContinueWith方法。

class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(WorkPro);//no.1
            t.IsBackground = true;//no.2
            t.Start(1000);//no.3
        }
        public static void WorkPro(object  t)
        {
            //做一些耗时的工作   
            int count=(int)t;
            for (int i = 0; i < count; i++)
            {
                Thread.Sleep(2000);
            }

            Console.WriteLine("任务处理完成");
        }
    }

三、小结

异步编制程序中相比关怀,也是相比首要的技艺点在于,1)当异步线程在办事成就时怎么着打招呼调用线程,2)当异步线程现身卓殊的时候该如哪里理,3)异步线程专门的工作的快慢怎样实时的文告调用线程。4)怎么着在调用线程中废除正在专门的学问的异步线程,并开展回滚操作。

1、Thread类

class Program
    {
        public delegate int DoWord(int count);
        static void Main(string[] args)
        {
            DoWord d = new DoWord(WorkPro);
            IAsyncResult r= d.BeginInvoke(100,CallBack ,d);//no.1
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(10);//主线程需要做的事
            }
            Console.WriteLine("主线程done");
            Console.ReadKey();
        }
        public static int WorkPro(int count)
        {
            int sum = 0;
            //做一些耗时的工作
            for (int i = 0; i < count; i++)
            {
                sum += i;
                Thread.Sleep(10);
            }
            return sum;        
        }

        public static void CallBack(IAsyncResult r)
        {
            DoWord d = (DoWord)r.AsyncState;
            Console.WriteLine("异步调用完成,返回结果为{0}", d.EndInvoke(r));
        }
    }

 

看上边风流罗曼蒂克段代码如何演示打消正在运作的职分。

 

Task类是包装的多个职责类,内部选用的是ThreadPool类,提供了内建建制,让你了然如几时候异步完结甚至哪些赢得异步执行的结果,并且还是能撤销异步试行的天职。上面看一个例证是什么行使Task类来实施异步操作的。

.net在System.ThreadingSystem.Threading.Tasks那四个命名空间中提供了ThreadThreadPool,和Task多少个类来管理多线程的标题,个中Thread是确立多少个专项使用线程,ThreadPool是使用线程池湖北中华南理管理高校程公司作线程,而Task类是利用职分的秘诀,此中间也是使用线程池中的职业线程。本节只讲Tread类和Tasks类的使用以至其优劣。


在no.1处照旧和第三个例子同样调用委托,差别的是用IAsyncResult接口的变量选取了异步调用(并非异步函数)的回到状态,那是方便人民群众前面调用EndInvoke方法选用这几个异步函数调用结果而利用的,也得以经过该参数查看异步函数施行的景观,该接口有三个IsCompleted的属性。在no.2处使用d.EndInvoke(r)来经受异步函数再次回到值的。必得提出的是,主线程在调用委托的EndInvoke(r)措施时,当异步函数未有推行完毕的话,主线程会平昔处在窒碍,等待异步函数实行达成,获取重返值之后才实践no.3的for巡回。那样就还大概会引致主线程处于梗塞状态。

二、七十多线程模型

 

Thread类的利用办法很简短,它开拓的是二个专项使用线程,不是线程池中的职业线程,不由线程池去管理。该类提供4个重载版本,司空眼惯的运用前面四个就好了。

class Program
    {
        static void Main(string[] args)
        {           
            Task<int> t = new Task<int>((c) =>Sum((int)c), 100);
            t.Start();
            t.ContinueWith(task => Console.WriteLine("任务完成的结果{0}", task.Result));//当任务执行完之后执行
            t.ContinueWith(task => Console.WriteLine(""), TaskContinuationOptions.OnlyOnFaulted);//当任务出现异常时才执行
            for (int i = 0; i < 200; i++)
            {
                Thread.Sleep(10);
            }
            Console.WriteLine("done");
        }
        static int Sum( int count)
        {
            int sum = 0;
            for (int i = 0; i < count; i++)
            {       
                    Thread.Sleep(10);
                    sum += i;          
            }
            Console.WriteLine("任务处理完成");
            return sum;
        }
    }

t.Start()之后调用第一个ContinueWith方法,该方法第一参数就是一个Action<Task>的委托类型,相当于是一个回调函数,在这里我也用lambda表达式,当任务完成就会启用一个新任务去执行这个回调函数。而第二个ContinueWith里面的回调方法却不会执行,因为我们的任务也就是Sum方法不会发生异常,不能满足TaskContinuationOptions.OnlyOnFaulted这个枚举条件。这种用法比委托的异步函数编程看起来要简单些。最关键的是ContinueWith的还有一个重载版本可以带一个TaskScheduler对象参数,该对象负责执行被调度的任务。FCL中提供两种任务调度器,均派生自TaskScheduler类型:线程池调度器,和同步上下文任务调用器。而在Winform窗体程序设计中TaskScheduler尤为有用,为什么这么说呢?因为在窗体程序中的控件都是有ui线程去创建,而我们所执行的后台任务使用线程都是线程池中的工作线程,所以当我们的任务完成之后需要反馈到Winform控件上,但是控件创建的线程和任务执行的线程不是同一个线程,如果在任务线程中去更新控件就会导致控件对象安全问题会出现异常。所以操作控件,就必须要使用ui线程去操作。因此在ContinueWith获取任务执行的结果的并反馈到控件的任务调度上不能使用线程池任务调用器,而要使用同步上下文任务调度器去调度,即采用ui这个线程去调用ContinueWith方法所绑定的回调用函数即Action<Task>类型的委托。下面将使用任务调度器来把异步执行的Sum计算结果反馈到Winform界面的TextBox控件中。

界面如下。

异步编制程序是前后相继设计的重大也是难关,还记得在刚起初接触.net的时候,看的是一本c#的Winform实例教程,上面半数以上都以教大家什么样使用Winform的控件甚至操作数据库的实例,这个时候做的基本都以数据库的demo,数据量也超小,程序在履行的时候大致不会师世阻塞的景色。随着不断的深入.net,也初始步入的实战,在实质上的种类,数据量往往都以比极大,非常是在多量的数目入库以至询问数据并拓宽测算的时候,程序的UI分界面往往卡死在那,产生了堵截,这个时候就供给对计量时间限制的长河举行异步管理,让UI线程继续相应客户的操作,使得客商体验表现相比较协和,同期精确的使用异步编制程序去管理总计范围的操作和耗费时间IO操作还能够晋升的应用程序的吞吐量及品质。一句话来说,异步编制程序的主要。