【转载】C#之异步
<div class="post"><h1 class="postTitle"> <span>C#之异步</span> <button class="cnblogs-toc-button" title="显示目录导航"></button></h1>
<div class="clear"> </div>
<div class="postBody">
<div id="cnblogs_post_body" class="blogpost-body cnblogs-markdown">
<h1 id="c之异步">C#之异步<button class="cnblogs-toc-button" title="显示目录导航"></button></h1>
<p>在计算机中,一个线程就是一系列的命令,一个工作单元。操作系统可以管理多个线程,给每个线程分配cpu执行的时间片,然后切换不同的线程在这个cpu上执行。这种单核的处理器一次只能做一件事,不能同时做两件以上的事情,只是通过时间的分配来实现多个线程的执行。但是在多核处理器上,可以实现同时执行多个线程。操作系统可以将时间分配给第一个处理器上的线程,然后在另一个处理器上分配时间给另一个线程。</p>
<p><strong>异步是相对于同步而言。跟多线程不能同一而论。</strong></p>
<p>异步编程采用future或callback机制,以避免产生不必要的线程。(<em>一个future代表一个将要完成的工作。</em>)异步编程核心就是:启动了的操作将在一段时间后完成。这个操作正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当操作完成时,会通知它的future或者回调函数,以便让程序知道操作已经结束。</p>
<p>为什么要使用异步:</p>
<p>面向终端用户的GUI程序:异步编程提高了相应能力。可以使程序在执行任务时仍能相应用户的输入。<br>
服务器端应用:实现了可扩展性。服务器应用可以利用线程池满足其可扩展性。</p>
<hr>
<h3 id="异步和同步的区别">异步和同步的区别:<button class="cnblogs-toc-button" title="显示目录导航"></button></h3>
<p>如果以同步方式执行某个任务时,需要等待该任务完成,然后才能再继续执行另一个任务。而用异步执行某个任务时,可以在该任务完成之前执行另一个任务。<strong>异步最重要的体现就是不排队,不阻塞</strong>。</p>
<p>图:单线程同步<br>
<img src="https://images2017.cnblogs.com/blog/691772/201708/691772-20170810223910355-321198728.png" alt="" class="medium-zoom-image" loading="lazy"></p>
<p>图:多线程同步<br>
<img src="https://images2017.cnblogs.com/blog/691772/201708/691772-20170810223835339-742257738.png" alt="" class="medium-zoom-image" loading="lazy"></p>
<hr>
<h3 id="异步跟多线程">异步跟多线程<button class="cnblogs-toc-button" title="显示目录导航"></button></h3>
<p>异步可以在单个线程上实现,也可以在多个线程上实现,还可以不需要线程(一些IO操作)。</p>
<p>图:单线程异步<br>
<img src="https://images2017.cnblogs.com/blog/691772/201708/691772-20170810223919589-1050900724.png" alt="" class="medium-zoom-image" loading="lazy"></p>
<p>图:多线程异步<br>
<img src="https://images2017.cnblogs.com/blog/691772/201708/691772-20170810223926574-1441395531.png" alt="" class="medium-zoom-image" loading="lazy"></p>
<hr>
<h3 id="异步是否创建线程">异步是否创建线程<button class="cnblogs-toc-button" title="显示目录导航"></button></h3>
<p>异步可以分为CPU异步和IO异步。异步在CPU操作中是必须要跑在线程上的,一般情况下这时我们都会新开一个线程执行这个异步操作。但在IO操作中是不需要线程的,硬件直接和内存操作。<br>
但是是否创建线程取决于你的异步的实现方式。比如在异步你用ThreadPool,Task.Run()等方法是创建了一个线程池的线程,那么该异步是在另一个线程上执行。</p>
<hr>
<p><strong>C#实现异步的四种方式:</strong></p>
<ol>
<li>异步模式BeginXXX,EndXXX</li>
<li>事件异步xxxAsync,xxxCompleted</li>
<li>基于任务<code>Task</code>的异步</li>
<li><code>async</code>,<code>await</code>关键字异步</li>
</ol><hr>
<h2 id="异步模式">异步模式<button class="cnblogs-toc-button" title="显示目录导航"></button></h2>
<p>异步模式是调用<code>Beginxxx</code>方法,返回一个<code>IAsyncResult</code>类型的值,在回调函数里调用<code>Endxxxx(IAsyncResult)</code>获取结果值。</p>
<p>异步模式中最常见的是委托的异步。</p>
<p>如:声明一个string类型输入参数和string类型返回值的委托。调用委托的BeginInvoke方法,来异步执行该委托。</p>
<pre><code class="language-CSharp hljs"> Func<<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>> func = (<span class="hljs-built_in">string</span> str) =>
{
Console.WriteLine(str);
<span class="hljs-keyword">return</span> str + <span class="hljs-string">" end"</span>;
};
func.BeginInvoke(<span class="hljs-string">"hello"</span>,IAsyncResult ar =>
{
Console.WriteLine(func.EndInvoke(ar));
}, <span class="hljs-literal">null</span>);
<span class="hljs-comment">//输出:</span>
<span class="hljs-comment">//hello</span>
<span class="hljs-comment">//hello end</span>
</code></pre>
<p><code>BeginInvoke</code>方法的第一个参数表示委托的输入参数。</p>
<p>第二个参数表示<code>IAsyncResult</code>类型输入参数的回调函数,其实也是个委托。</p>
<p>第三个参数是个状态值。</p>
<hr>
<h2 id="事件异步">事件异步<button class="cnblogs-toc-button" title="显示目录导航"></button></h2>
<p>事件异步有一个<code>xxxAsync</code>方法,和对应该方法的 <code>xxxCompleted</code>事件。</p>
<p>如: <code>backgroundworker</code>和<code>progressbar</code>结合</p>
<pre><code class="language-CSharp hljs">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainWindow</span> : <span class="hljs-title">Window</span>
{
<span class="hljs-keyword">private</span> BackgroundWorker bworker = <span class="hljs-keyword">new</span> BackgroundWorker();
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainWindow</span>()</span>
{
InitializeComponent();
<span class="hljs-comment">//支持报告进度</span>
bworker.WorkerReportsProgress = <span class="hljs-literal">true</span>;
<span class="hljs-comment">//执行具体的方法</span>
bworker.DoWork += Bworker_DoWork;
<span class="hljs-comment">//进度变化时触发的事件</span>
bworker.ProgressChanged += Bworker_ProgressChanged;
<span class="hljs-comment">//异步结束时触发的事件</span>
bworker.RunWorkerCompleted += Bworker_RunWorkerCompleted;
Loaded += MainWindow_Loaded;
}
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">MainWindow_Loaded</span>(<span class="hljs-params"><span class="hljs-built_in">object</span> sender, RoutedEventArgs e</span>)</span>
{
<span class="hljs-comment">//开始异步执行</span>
bworker.RunWorkerAsync();
}
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Bworker_RunWorkerCompleted</span>(<span class="hljs-params"><span class="hljs-built_in">object</span> sender, RunWorkerCompletedEventArgs e</span>)</span>
{
<span class="hljs-comment">//异步完成时触发的事件</span>
progressBar.<span class="hljs-keyword">value</span>=<span class="hljs-number">100</span>;
}
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Bworker_ProgressChanged</span>(<span class="hljs-params"><span class="hljs-built_in">object</span> sender, ProgressChangedEventArgs e</span>)</span>
{
<span class="hljs-comment">//获取进度值复制给progressBar</span>
progressBar.Value = e.ProgressPercentage;
}
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Bworker_DoWork</span>(<span class="hljs-params"><span class="hljs-built_in">object</span> sender, DoWorkEventArgs e</span>)</span>
{
<span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> j = <span class="hljs-number">0</span>; j <= <span class="hljs-number">100</span>; j++)
{
<span class="hljs-comment">//调用进度变化方法,触发进度变化事件</span>
bworker.ReportProgress(j);
Thread.Sleep(<span class="hljs-number">100</span>);
}
}
}
</code></pre>
<hr>
<h2 id="task模式的异步">Task模式的异步<button class="cnblogs-toc-button" title="显示目录导航"></button></h2>
<p><code>Task</code>是在Framework4.0提出来的新概念。<code>Task</code>本身就表示一个异步操作(<em><code>Task</code>默认是运行在线程池里的线程上</em>)。它比线程更轻量,可以更高效的利用线程。并且任务提供了更多的控制操作。</p>
<ul>
<li>实现了控制任务执行顺序</li>
<li>实现父子任务</li>
<li>实现了任务的取消操作</li>
<li>实现了进度报告</li>
<li>实现了返回值</li>
<li>实现了随时查看任务状态</li>
</ul>
<p>任务的执行默认是由任务调度器来实现的(<em>任务调用器使这些任务并行执行</em>)。任务的执行和线程不是一一对应的。有可能会是几个任务在同一个线程上运行,充分利用了线程,避免一些短时间的操作单独跑在一个线程里。所以任务更适合CPU密集型操作。</p>
<h4 id="task-启动">Task 启动<button class="cnblogs-toc-button" title="显示目录导航"></button></h4>
<p>任务可以赋值立即运行,也可以先由构造函数赋值,之后再调用。</p>
<pre><code class="language-CSharp hljs"><span class="hljs-comment">//启用线程池中的线程异步执行</span>
Task t1 = Task.Factory.StartNew(() =>
{
Console.WriteLine(<span class="hljs-string">"Task启动..."</span>);
});
<span class="hljs-comment">//启用线程池中的线程异步执行</span>
Task t2 = Task.Run(() =>
{
Console.WriteLine(<span class="hljs-string">"Task启动..."</span>);
});
Task t3 = <span class="hljs-keyword">new</span> Task(() =>
{
Console.WriteLine(<span class="hljs-string">"Task启动..."</span>);
});
t3.Start();<span class="hljs-comment">//启用线程池中的线程异步执行</span>
t3.RunSynchronously();<span class="hljs-comment">//任务同步执行</span>
</code></pre>
<h4 id="task-等待任务结果处理结果">Task 等待任务结果,处理结果<button class="cnblogs-toc-button" title="显示目录导航"></button></h4>
<pre><code class="language-CSharp hljs"> Task t1 = Task.Run(() =>
{
Console.WriteLine(<span class="hljs-string">"Task启动..."</span>);
});
Task t2 = Task.Run(() =>
{
Console.WriteLine(<span class="hljs-string">"Task启动..."</span>);
});
<span class="hljs-comment">//调用WaitAll() ,会阻塞调用线程,等待任务执行完成 ,这时异步也没有意义了 </span>
Task.WaitAll(<span class="hljs-keyword">new</span> Task[] { t1, t2 });
Console.WriteLine(<span class="hljs-string">"Task完成..."</span>);
<span class="hljs-comment">//调用ContinueWith,等待任务完成,触发下一个任务,这个任务可当作任务完成时触发的回调函数。</span>
<span class="hljs-comment">//为了获取结果,同时不阻塞调用线程,建议使用ContinueWith,在任务完成后,接着执行一个处理结果的任务。</span>
t1.ContinueWith((t) =>
{
Console.WriteLine(<span class="hljs-string">"Task完成..."</span>);
});
t2.ContinueWith((t) =>
{
Console.WriteLine(<span class="hljs-string">"Task完成..."</span>);
});
<span class="hljs-comment">//调用GetAwaiter()方法,获取任务的等待者,调用OnCompleted事件,当任务完成时触发</span>
<span class="hljs-comment">//调用OnCompleted事件也不会阻塞线程</span>
t1.GetAwaiter().OnCompleted(() =>
{
Console.WriteLine(<span class="hljs-string">"Task完成..."</span>);
});
t2.GetAwaiter().OnCompleted(() =>
{
Console.WriteLine(<span class="hljs-string">"Task完成..."</span>);
});
</code></pre>
<h4 id="task-任务取消">Task 任务取消<button class="cnblogs-toc-button" title="显示目录导航"></button></h4>
<pre><code class="language-CSharp hljs"><span class="hljs-comment">//实例化一个取消实例</span>
<span class="hljs-keyword">var</span> source = <span class="hljs-keyword">new</span> CancellationTokenSource();
<span class="hljs-keyword">var</span> token = source.Token;
Task t1 = Task.Run(() =>
{
Thread.Sleep(<span class="hljs-number">2000</span>);
<span class="hljs-comment">//判断是否任务取消</span>
<span class="hljs-keyword">if</span> (token.IsCancellationRequested)
{
<span class="hljs-comment">//token.ThrowIfCancellationRequested();</span>
Console.WriteLine(<span class="hljs-string">"任务已取消"</span>);
}
Thread.Sleep(<span class="hljs-number">500</span>);
<span class="hljs-comment">//token传递给任务</span>
}, token);
Thread.Sleep(<span class="hljs-number">1000</span>);
Console.WriteLine(t1.Status);
<span class="hljs-comment">//取消该任务</span>
source.Cancel();
Console.WriteLine(t1.Status);
</code></pre>
<h4 id="task-返回值">Task 返回值<button class="cnblogs-toc-button" title="显示目录导航"></button></h4>
<pre><code class="language-CSharp hljs">Task<<span class="hljs-built_in">string</span>> t1 = Task.Run(() => TaskMethod(<span class="hljs-string">"hello"</span>));
t1.Wait();
Console.WriteLine(t1.Result);
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-built_in">string</span> <span class="hljs-title">TaskMethod</span>(<span class="hljs-params"><span class="hljs-built_in">string</span> str</span>)</span>
{
<span class="hljs-keyword">return</span> str + <span class="hljs-string">" from task method"</span>;
}
</code></pre>
<p><strong>Task异步操作,需要注意的一点就是调用Waitxxx方法,会阻塞调用线程。</strong></p>
<hr>
<h2 id="async-await-异步">async await 异步<button class="cnblogs-toc-button" title="显示目录导航"></button></h2>
<p>首先要明确一点的就是<code>async</code> <code>await</code> 不会创建线程。并且他们是一对关键字,必须成对的出现。</p>
<p>如果<code>await</code>的表达式没有创建新的线程,那么一个异步操作就是在调用线程的时间片上执行,否则就是在另一个线程上执行。</p>
<pre><code class="language-CSharp hljs"><span class="hljs-function"><span class="hljs-keyword">async</span> Task <span class="hljs-title">MethodAsync</span>()</span>
{
Console.WriteLine(<span class="hljs-string">"异步执行"</span>);
<span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">4000</span>);
Console.WriteLine(<span class="hljs-string">"异步执行结束"</span>);
}
</code></pre>
<p>一个异步方法必须有<code>async</code>修饰,且方法名以Async结尾。异步方法体至少包含一个<code>await</code>表达式。<code>await</code> 可以看作是一个挂起异步方法的一个点,且同时把控制权返回给调用者。异步方法的返回值必须是<code>Task</code>或者<code>Task<T></code>。即如果方法没有返回值那就用Task表示,如果有一个string类型的返回值,就用Task泛型<code>Task<string></code>修饰。</p>
<p>异步方法执行流程:</p>
<ol>
<li>主线程调用MethodAsync方法,并等待方法执行结束</li>
<li>异步方法开始执行,输出“异步执行”</li>
<li>异步方法执行到await关键字,此时MethodAsync方法挂起,等待await表达式执行完毕,同时将控制权返回给调用方主线程,主线程继续执行。</li>
<li>执行Task.Delay方法,同时主线程继续执行之后的方法。</li>
<li><code>Task.Delay</code>结束,<code>await</code>表达式结束,MehtodAsync执行await表达式之后的语句,输出“异步执行结束”。</li>
</ol>
<p>和其他方法一样,async方法开始时以同步方式执行。在async内部,await关键字对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则它会暂停async方法,并返回,留下一个未完成的Task。一段时间后,操作完成,async方法就恢复运行。</p>
<p>一个async方法是由多个同步执行的程序块组成的,每个同步程序块之间由await语句分隔。第一个同步程序块是在调用这个方法的线程中执行,但其他同步程序块在哪里运行呢?情况比较复杂。</p>
<p>最常见的情况是用await语句等待一个任务完成,当该方法在await处暂停时,就可以捕获上下文(context)。如果当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext。如果为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行。一般来说,运行在UI线程时采用UI上下文,处理Asp.Net请求时采用Asp.Net请求上下文,其他很多情况下则采用线程池上下文。</p>
<p>因为,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在UI线程调用async方法,该方法的每个同步程序块都将在此UI线程上运行。但是,如果在线程池中调用,每个同步程序块将在线程池上运行。</p>
<p>如果要避免这种行为,可以在await中使用configureAwait方法,将参数ContinueOnCapturedContext设置为false。async方法中await之前的代码会在调用的线程里运行。在被await暂停后,await之后的代码则会在线程池里继续运行。</p>
<pre><code class="language-CSharp hljs"><span class="hljs-function"><span class="hljs-keyword">async</span> Task <span class="hljs-title">MethodAsync</span>()</span>
{
Console.WriteLine(<span class="hljs-string">"异步执行"</span>);<span class="hljs-comment">//同步程序块1</span>
<span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">4000</span>).ConfigureAwait(<span class="hljs-literal">false</span>);
Console.WriteLine(<span class="hljs-string">"异步执行结束"</span>);<span class="hljs-comment">//同步程序块2</span>
}
</code></pre>
<p>我们可能想当然的认为<code>Task.Delay</code>会阻塞执行线程,就跟<code>Thread.Sleep</code>一样。其实他们是不一样的。<code>Task.Delay</code>创建一个将在设置时间后执行的任务。就相当于一个定时器,多少时间后再执行操作。不会阻塞执行线程。</p>
<p>当我们在异步线程中调用Sleep的时候,只会阻塞异步线程。不会阻塞到主线程。</p>
<pre><code class="language-CSharp hljs"><span class="hljs-function"><span class="hljs-keyword">async</span> Task <span class="hljs-title">Method2Async</span>()</span>
{
Console.WriteLine(<span class="hljs-string">"await执行前..."</span>+Thread.CurrentThread.ManagedThreadId);
<span class="hljs-keyword">await</span> Task.Run(() =>
{
Console.WriteLine(<span class="hljs-string">"await执行..."</span> + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(<span class="hljs-number">5000</span>);
Console.WriteLine(<span class="hljs-string">"await执行结束..."</span> + Thread.CurrentThread.ManagedThreadId);
});
Console.WriteLine(<span class="hljs-string">"await之后执行..."</span>+ Thread.CurrentThread.ManagedThreadId);
}
<span class="hljs-comment">//输出:</span>
<span class="hljs-comment">//await执行前...9</span>
<span class="hljs-comment">//await执行...12</span>
<span class="hljs-comment">//await之后执行...9</span>
<span class="hljs-comment">//await执行结束...12</span>
</code></pre>
<p>上面的异步方法,<code>Task</code>创建了一个线程池线程,Thread.Sleep执行在线程池线程中。</p>
<hr>
<p>参考:</p>
<ul>
<li>
<p>Async</p>
</li>
<li>
<p>Task</p>
</li>
<li>
<p>StackOverflow</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="MySignature" role="contentinfo">
本文作者:Love In Winter<hr/>
本文链接:https://www.cnblogs.com/LifeDecidesHappiness/p/15718173.html<hr/>
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!<hr/>
声援博主:如果您觉得文章对您有帮助,可以扫一扫,任意打赏,您的鼓励是博主的最大动力!<hr/>
<img src="https://files.cnblogs.com/files/LifeDecidesHappiness/%E6%94%AF%E4%BB%98%E5%AE%9D.bmp" alt="扫一扫,支付宝打赏"/>
<img src="https://files.cnblogs.com/files/LifeDecidesHappiness/%E5%BE%AE%E4%BF%A1%E6%94%B6%E6%AC%BE%E7%A0%81.bmp" alt="扫一扫,微信打赏"/><br><br>
来源:https://www.cnblogs.com/LifeDecidesHappiness/p/15718173.html
頁:
[1]