为什么要引入异步?Result的死锁陷阱/同步方程里使用异步程式/GetAwaiter().GetResult()

http://www.cnblogs.com/zhaopei/p/async_two.html

什么是吞吐量呢,也就是本来只能十个人同时访问的网站现在可以二十个人同时访问了。也就是常说的并发量

们的Web程序天生就是多线程的,且web线程都是跑的线程池线程(使用线程池线程是为了避免不断创建、销毁线程所造成的资源成本浪费),

线程池线程可使用线程数量是一定的,尽管可以设置但它还是会在一定范围内。如此一来,我们线程是珍贵的(物以稀为贵),不能滥用

线程数用完后,再请求的时候就直接503

那什么算是滥用呢?比如:文件读取、URL请求、数据库访问等IO请求。

如果用web线程来做这个耗时的IO操作那么就会阻塞web线程,而web线程阻塞得多了web线程池线程就不够用了。也就达到了web程序最大访问数。

此时我们的新异步横空出世,解放了那些原本处理IO请求而阻塞的web线程(想偷懒?没门,干活了。)。

通过异步方式使用相对廉价的线程(非web线程池线程)来处理IO操作,这样web线程池线程就可以解放出来处理更多的请求了。

仔细观察,每个单次请求用时基本上相差不大。 但是步骤7"同步实现"最高投入web线程数是10,而步骤8“异步实现”最高投入web线程数是3。

异步实现:使用更少的web线程完成了同样的请求数量,如此一来我们就有更多剩余的web线程去处理更多用户发起的请求。

接着我们还发现同步实现请求前后的线程ID是一致的,而异步实现前后线程ID不一定一致。再次证明执行await异步前释放了主线程。

【结论】:

  • 使用新异步可以提升Web服务程序的吞吐量
  • 对于客户端来说,web服务的异步并不会提高客户端的单次访问速度。
  • 执行新异步前会释放web线程,而等待异步执行完成后又回到了web线程上。从而提高web线程的利用率。

Result的死锁陷阱

我们在分析UI单线程程序的时候说过,要慎用异步的Result属性。下面我们来分析:

复制代码
private void button4_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
//执行await 前会释放UI线程
//但是GetStringAsync().Result的Result属性会占用UI线程 return await http.GetStringAsync(url); } }
复制代码

代码 GetUlrString("https://github.com/").Result 的Result属性会阻塞(占用)UI线程

而执行到GetUlrString方法的 await异步的时候又要释放UI线程此时矛盾就来了,由于线程资源的抢占导致死锁

Result属性和.Wait()方法一样会阻塞线程。此等问题在Web服务程序里面一样存在。

(区别:UI单次线程程序和web服务程序都会释放主线程,不同的是Web服务线程不一定会回到原来的主线程,而UI程序一定会回到原来的UI线程)

我们前面说过,.net为什么会这么智能的自动释放主线程然后等待异步执行完毕后又回到主线程是因为SynchronizationContext的功劳。

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = GetUrlString("http://www.jb51.net/article/57156.htm").Result;
    //Result属性会阻塞( 占用)UI线程,而执行GetUrlString方法的异步的时候又要释放UI线程。
    //此时就矛盾了,由于线程资源的抢占导致死锁
    //且Result属性和.Wait()方法一样会阻塞线程。
    //结果是卡死啦
}
public async Task < string > GetUrlString(string url)
{
    using(HttpClient http = new HttpClient())
    {
        //return await http.GetStringAsync(url);//会出现死锁
        //除了AsyncHelper我们还可以使用Task的ConfigureAwait方法来避免死锁
        return await http.GetStringAsync(url).ConfigureAwait(false);
        //ConfigureAwait(false)会解决死锁
        //ConfigureAwait的作用:使当前async方法的await后续操作不需要恢复到主线程(不需要保存线程上下文)。
    }
}

使用AsyncHelper在同步代码里面调用异步

但可是,可但是,我们必须在同步方法里面执行异步怎办?办法肯定是有的

我们首先定义一个AsyncHelper静态类:

复制代码
static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new TaskFactory(
CancellationToken.None, TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default
); public static TResult RunSync<TResult>(Func<Task<TResult>> func) { return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); } public static void RunSync(Func<Task> func) { _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); } }
复制代码

然后调用异步:

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = AsyncHelper.RunSync(() => GetUlrString("https://github.com/"));
}

这样就不会死锁了。

在使用那个异步的PostAsync(),后面可以跟这两个方法,等待消息的回传
GetAwaiter().GetResult()

.Wait()与.GetAwaiter()之间有什么区别
两者都是同步等待操作的结果
差异主要在于处理异常.使用Wait,异常堆栈跟踪不会改变并表示异常时的实际堆栈,因此如果您有一段代码在线程池线程上运行,那么您将拥有类似的堆栈

 其实这两个使用方式是差不多的。不过,还是有一点小小的区别的:
 如果任务失败,Task.GetAwaiter().GetResult()会直接抛出异常而Task.Result则会把异常包装在AggregateException中
 从这个角度说Task.GetAwaiter().GetResult()要优于Task.Result。
 毕竟它少了异常的包装操作,即直接抛出异常,而不是把异常包装在AggregateException中。


https://blog.csdn.net/qq_46388795/article/details/106874460

 

 

posted @ 2017-09-27 16:26  ProZkb  阅读(1161)  评论(0编辑  收藏  举报