第二十章 异步编程

2018-11-28  本文已影响0人  向着远方奔跑

async/await特性的结构

一个程序调用某个方法,等待其执行所有处理后才继续执行,这种方法是同步的。
异步的方法在处理完成之前就返回到调用方法。C#的async/await特性可以创建并使用异步方法。由三部分组成:

识别 CPU 绑定和 I/O 绑定工作

在使用异步编程前,首先应确定我们要执行的操作时 I/O绑定 还是 CPU绑定 ,因为这会极大影响代码性能,并可能导致某些构造的误用。

以下是编写代码前应考虑的两个问题:

如果你的工作为 I/O 绑定,请使用 asyncawait(而不使用 Task.Run)。 不应使用任务并行库。 相关原因在深入了解异步的文章中说明。

如果你的工作为 CPU 绑定,并且你重视响应能力,请使用 asyncawait,并在另一个线程上使用 Task.Run生成工作。 如果该工作同时适用于并发和并行,则应考虑使用任务并行库

此外,应始终对代码的执行进行测量。 例如,你可能会遇到这样的情况:多线程处理时,上下文切换的开销高于 CPU 绑定工作的开销。 每种选择都有折衷,应根据自身情况选择正确的折衷方案。

class Program
{
    static void main()
    {
        ...
        Task<int> value = DoAsyncStuff.CalculateSumAsync(5, 6);//调用方法
        ...
    }
}

static class DoAsyncStuff
{
                 关键字  返回类型
                    ↓      ↓
    public static async Task<int> CalculateSumAsync(int i1, int i2)//异步方法
    {
        int sum = await Task.Run( () => GetSum(i1, i2));//await表达式
        return sum;//返回语句
    }
    ...
}

异步方法

async Task<int> CountCharactersAsync( int id, string site )
{
    Console.WriteLine( "Starting CountCharacters" );
    WebClient wc = new WebClient();

    string result = await wc.DownloadStringTaskAsync( new Uri( site ) );//await表达式

    Console.WriteLine( "CountCharacters Completed" );
    return result.Length;
}
返回类型
Task<int> value = DoStuff.CalculateSumAsync(5, 6);
...
Console.WriteLine( "Value: {0}", value.Result );
class AsyncReturnTest1
    {
        public void Excute()
        {
            Task<int> value = DoAsyncStuff.CalculateSumAsync(5, 6);
            Console.WriteLine("value: {0}", value.Result);
        }

        class DoAsyncStuff
        {
            public static async Task<int> CalculateSumAsync(int i1, int i2)
            {
                int sum = await Task.Run(() => GetSum(i1, i2));
                return sum;
            }

            private static int GetSum(int i1, int i2)
            {
                return i1 + i2;
            }
        }
    }
Task someTask = DoStuff.CalculateSumAsync(5, 6);
...
someTask.Wait();//等待Task完成执行过程
Console.WriteLine("Async stuff is done: {0}", someTask.Status);//可以使用.Status获取Task的执行的返回状态,若完成则为“RanToCompletion”
class AsyncReturnTest2
    {
        public void Excute()
        {
            Task someTask = DoAsyncStuff.CalculateSumAsync(5, 6);
            someTask.Wait();
            Console.WriteLine("Async stuff is done: {0}", someTask.Status);
        }

        class DoAsyncStuff
        {
            public static async Task CalculateSumAsync(int i1, int i2)
            {
                int value = await Task.Run(() => GetSum(i1, i2));
                Console.WriteLine("value:{0}", value);
            }

            private static int GetSum(int i1, int i2)
            {
                return i1 + i2;
            }
        }
    }
class AsyncReturnTest3
    {
        public void Excute()
        {
            DoAsyncStuff.CalculateSumAsync(5, 6);
            Thread.Sleep(200);
            Console.WriteLine("Program Exiting");
        }

        class DoAsyncStuff
        {
            public static async void CalculateSumAsync(int i1, int i2)
            {
                int value = await Task.Run(() => GetSum(i1, i2));
                Console.WriteLine("value: {0}", value);
            }

            private static int GetSum(int i1, int i2)
            {
                return i1 + i2;
            }
        }
    }

重要信息和建议

尽管异步编程相对简单,但应记住一些可避免意外行为的要点。

这一点需牢记在心。 如果 await 未用在 async 方法的主体中,C# 编译器将生成一个警告,但此代码将会以类似普通方法的方式进行编译和运行。 请注意这会导致效率低下,因为由 C# 编译器为异步方法生成的状态机将不会完成任何任务。

这是 .NET 中的惯例,以便更轻松区分同步和异步方法。 请注意,未由代码显式调用的某些方法(如事件处理程序或 Web 控制器方法)并不一定适用。 由于它们未由代码显式调用,因此对其显式命名并不重要。

async void 是允许异步事件处理程序工作的唯一方法,因为事件不具有返回类型(因此无法利用 TaskTask<T>)。 其他任何对 async void 的使用都不遵循 TAP 模型,且可能存在一定使用难度,例如:

LINQ 中的 Lambda 表达式使用延迟执行,这意味着代码可能在你并不希望结束的时候停止执行。 如果编写不正确,将阻塞任务引入其中时可能很容易导致死锁。 此外,此类异步代码嵌套可能会对推断代码的执行带来更多困难。 Async 和 LINQ 的功能都十分强大,但在结合使用两者时应尽可能小心。

将阻止当前线程作为等待任务完成的方法可能导致死锁和已阻止的上下文线程,且可能需要更复杂的错误处理。下表提供了关于如何以非阻止方式处理等待任务的指南:

使用以下方式... 而不是… 若要执行此操作
await Task.WaitTask.Result 检索后台任务的结果
await Task.WhenAny Task.WaitAny 等待任何任务完成
await Task.WhenAll Task.WaitAll 等待所有任务完成
await Task.Delay Thread.Sleep 等待一段时间

请勿依赖全局对象的状态或某些方法的执行。 请仅依赖方法的返回值。 为什么?

建议的目标是实现代码中完整或接近完整的引用透明度。 这么做能获得高度可预测、可测试和可维护的基本代码。

上一篇下一篇

猜你喜欢

热点阅读