C#多线程学习 十四:同步 VS 异步

同步 VS 异步

  1. 同步操作会返回调用者之前完成它的工作。
  2. 异步操作会在返回调用者之后去做它的大部分工作。
  3. 异步的方法为少见 , 会启用并发,因为它的工作会与调用者并行执行。
  4. 异步方法通常很快(立即) 就会返回调用者 , 所以叫非阻塞方法。
  5. 目前见到的大部分的异步方法都是通用目的:
    Thread.Start Task.Run 可以将Continuation 附加到Task的方法。

什么是异步编程?

  1. 异步编程的原则是将长时间运行的函数写成异步的。
  2. 传统的做法是 将长时间的运行的函数写成同步,然后从新的线程或者Task 进行调用,从而按需引用。
  3. 上述异步方式的不同之处在于,他是从长时间运行函数的 内部启用并发,还有两点好处:
    IO-bound 并发可不使用线程来实现。可以提高可拓展性和执行效率。 富客户端在worker 线程会使用更少的代码, 简化了线程安全性。

异步编程的两种用途

  1. 编写高效处理大量并发IO 的应用程序(典型的:服务器应用)
    挑战并是线程安全(因为共享状态通常是最小化的), 而是执行效率,特别是网络请求并不会消耗一个线程。

  2. 在富客户端应用简化线程安全。 如果调用图中任何一个操作是是长时间运行, 那么整个call graph 必须运行在 worker 线程上, 以保证UI响应。
    得到一个横跨多个方法的单一并发操作(颗粒度)。 需要为call graph 中每个方法考虑线程安全。 异步的call graph , 直到需要才开启一个线程,通常比较浅(IO-bound 操作完全不需要)

经验之谈:

  1. 为了获取上述好处,下列操作建议异步编程。
    IO-bound 和 Compute-bound 操作。
    执行操作时间超过了50ms。
  2. 另一边面过细的颗粒度会损害性能,因为异步操作也会有开销。

异步编程和Continuation

  1. Task 非常适合异步编程,因为他们支持Continuation (他对异步非常重要)
    TaskCompletionSource 的例子。
  2. 对于 Compute-bound 方法,Task.Run 会初始化绑定线程并发。
    把task 返回调用者 创建异步方法。
    异步编程的区别:目标是在调用图比较浅的位置。
    富客户端应用中,高级方法可以保留在UI线程和访问控制及共享状态上, 不会出现线程安全问题。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
DisplayPrimeCount();

        //Task.Run(()=> { DisplayPrimeCount(); });
        Console.ReadKey();
    }

    static void DisplayPrimeCount()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(GetPrimesCount(i * 100000 + 2, (i + 1) * 100000 - 1) + " primes between " + (i * 100000) + " and "+((i + 1) * 100000 - 1));
        }
        Console.WriteLine("Done");
    }

    static int GetPrimesCount(int start, int count)
    {
        return ParallelEnumerable.Range(start, count).Count(Ret =>
            Enumerable.Range(2, (int)Math.Sqrt(Ret) - 1).All(i => Ret % i > 0)

        ); 
    }
}

}

for text two

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
// DisplayPrimeCount();

        Task.Run(()=> { DisplayPrimeCount(); });

        Console.ReadKey();
    }

    static void DisplayPrimeCount()
    {
        //for (int i = 0; i < 10; i++)
        //{
        //    Console.WriteLine(GetPrimesCount(i * 100000 + 2, 1000000) + " primes between " + (i * 100000) + " and "+((i + 1) * 100000 - 1));
        //}

        for (int i = 0; i < 10; i++)
        {
            var awaiter = GetPrimesCountAsync(i * 100000 + 2, (i + 1) * 100000 - 1).GetAwaiter();
            awaiter.OnCompleted(() => {
                Console.WriteLine(awaiter.GetResult() + " primes between " + (i * 100000) + " and " + ((i + 1) * 100000 - 1));
            });

        }

        Console.WriteLine("Done");
    }

    static Task<int> GetPrimesCountAsync(int start, int count)
    {
        return Task<int>.Run(()=> ParallelEnumerable.Range(start, count).Count(Ret =>
            Enumerable.Range(2, (int)Math.Sqrt(Ret) - 1).All(i => Ret % i > 0)
        )); 
    }
}

}

语言对异步的支持是非常重要的

  1. 需要对task 的执行进行序列化, 例如 taskB 依赖于taskA 的执行结果,。
  2. 为此 , 必须在Continuation 内部触发下一次循环,
  3. async 和 await 对于不想复杂的实现异步是非常重要的,命令式循环结构不要和continuation 混合在一起, 应为他们依赖当前本地状态。
  4. 另一种实现, 函数式写法(linq查询) , 他们也是 响应式编程(rx)的基础。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
// DisplayPrimeCount();

        Task.Run(()=> { DisplayPrimeCount(); });

        Console.ReadKey();
    }

    static void DisplayPrimeCount()
    {
        DisplayPrimeCountFrom(0);
    }

    static void DisplayPrimeCountFrom(int start)
    {
      TaskAwaiter<int> awaiter=  GetPrimesCountAsync(start * 1000000 + 2, 1000000).GetAwaiter();
        awaiter.OnCompleted(() => {
            Console.WriteLine(awaiter.GetResult()+"\t"+start);
            if (start++<10)
            {
                DisplayPrimeCountFrom(start);
            }
        });
    }

    static Task<int> GetPrimesCountAsync(int start, int count)
    {
        return Task<int>.Run(()=> ParallelEnumerable.Range(start, count).Count(Ret =>
            Enumerable.Range(2, (int)Math.Sqrt(Ret) - 1).All(i => Ret % i > 0)
        )); 
    }
}

}

异步函数

async和 await 关键字 可以让你写出和同步代码一样的简介异步代码。

awaiting
await 关键字简化了附加continuation 的过程。
其结构如下: var result = await expression; statement(s);
它的作用相当于:
var awaiter= expression . GetAwaiter();
awaiter.OnCompleted(()=>{
var result=awaiter.GetResult();
statemnet(S);
});

async 修饰符

  1. async 修饰符会让编译器把await 当做关键字而不是标识符(C#5.0 以前可能会使用await 作为修饰符)
  2. async修饰符 只能适用于方法(包括lambda表达式)。 该方法可以返回void task task
  3. async 修饰符对方法签名和pubic 元素数据没有影响(和 unsafe 一样) 他只会影响方法内部。
  4. 在接口内部使用async是没有任何意义的。 使用async 来重载非async 的方法是合法的(只要方法签名一致)使用async 修饰符的方法就是“异步函数”

异步方法如何执行

  1. 遇到await 表达式,执行(正常情况下) 会返回调用者,就像iterator里边的 yield return。 再返回前 运行时会附加一个continuation 从停止的地方继续执行。
  2. 如果发生故障,那么异常会重新抛出。如果一切正常,那么他的返回值就会赋值给await 表达式。

可以 await 什么?

  1. 你await 的表达式通常是一个task。
  2. 也可以满足下列条件的任意对象: 有GetAwaiter方法,他返回一个awaiter。 返回适合类型的GetResult方法。 有一个bool类型的IsCompleted属性。

捕获本地状态:

  1. await 表达式的最牛之处 就是他几乎可以出现在任何地方。
  2. 特别的, 在异步方法内,await表达式可以替换任何表达式。
    除了lock 和 unsafe 上下文。

await 之后在哪个线程上执行

  1. await 表达式之后,编译器依赖于continuation(通过阿waiter模式) 来继续执行。
  2. 如果在富客户端应用的UI线程上,同步上下文会保证后续是在原线程上执行。否则, 就会在task 结束的线程上继续执行。

UI上的await

  1. 本例子中,只有GetPrimesCounAsync中的代码在worker 线程上执行,
  2. Go中的代码会租用UI线程上的时间。可以说:Go是在消息循环中 伪并发 的执行。也就是说:他和UI线程处理其他事件是穿插执行的。
  3. 因为这种伪并发, 唯一能发生强占的时刻就是在await 期间。 这其实简化了线程安全, 防止重复进入即可。
  4. 这种并发发生在调用栈较浅的地方(Task.Run 调用的代码)
  5. 为了从该模型获益,真正的并发代码要避免访问共享状态或UI控件。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

    void Go()
    {
        for (int i = 0; i < 100; i++)
        {
            textBox1.Text += GetPrimesCount(i + 100000, 100000).ToString()+Environment.NewLine;
        }
    }

    private int GetPrimesCount(int v1, int v2)
    {
        return ParallelEnumerable.Range(v1, v2).Count(ret =>
        {
          return   Enumerable.Range(2, (int)Math.Sqrt(ret) - 1).All(n => ret % n > 0);
        });
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        button1.Click += (_sender, _e) => Go();
    }
}

}

上面的例子是同步方法容易卡死UI主界面。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

   async void Go()
    {
        button1.Enabled = false;
        for (int i = 0; i < 100; i++)
        {
            textBox1.Text += await GetPrimesCountAsync(i + 1000000, 100000) + Environment.NewLine;
        }
        button1.Enabled = true;
    }

    private Task<int> GetPrimesCountAsync(int v1, int v2)
    {
        return Task<int>.Run(() => ParallelEnumerable.Range(v1, v2).Count(ret =>
    {
        return Enumerable.Range(2, (int)Math.Sqrt(ret) - 1).All(n => ret % n > 0);
    }));

    }

    private void Form1_Load(object sender, EventArgs e)
    {
        button1.Click += (_sender, _e) =>  Go();
    }
}

}

与颗粒度的并发相比

  1. 例如使用BackgroundWorker (例子, Task.Run)。 整个同步调用图都在worker 线程上。 必须在代码中到处使用Dispatcher.BeginInvoke
  2. 循环本身在worker 线程上。 引入 race condition 。 若实现取消和过程报告,会使得线程安全问题更容易发生,在方法中新添加任何的代码也是同样的效果。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

   async void Go()
    {
        button1.BeginInvoke(new Action(() => { button1.Enabled = false; }));
        

        for (int i = 0; i < 100; i++)
        {
            int retNu= await GetPrimesCountAsync(i + 1000000, 100000) ;
            textBox1.BeginInvoke(new Action(() => { textBox1.Text += retNu + Environment.NewLine; }));
        }

        button1.BeginInvoke(new Action(() => { button1.Enabled = true; }));
    }

    private Task<int> GetPrimesCountAsync(int v1, int v2)
    {
        return Task<int>.Run(() => ParallelEnumerable.Range(v1, v2).Count(ret =>
    {
        return Enumerable.Range(2, (int)Math.Sqrt(ret) - 1).All(n => ret % n > 0);
    }));

    }

    private void Form1_Load(object sender, EventArgs e)
    {
        button1.Click += (_sender, _e) =>Task.Run(()=> Go());
    }

   

    private void button1_Click(object sender, EventArgs e)
    {
       
    }
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

望天hous

你的鼓励是我最大动力~谢谢啦!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值