C#5

C#5.0

异步编程

.NET Framework中,有三种不同的模型来简化异步编程。

  1. .NET1.x中的异步编程模型(APM)方式(类似Beginxx/Endxx方法,使用IAsyncResultAsyncCallback来传播回调和结果)
  2. .NET2.0中的基于事件的异步模式(EAP)方式(类似xxAsync/xxCompletedEventHandler方法,使用事件EventArgs来传播回调和结果)
  3. .NET4引入并由.NET4.5扩展的任务并行库TPL基于任务的异步模式(TAP),使用Task来传播回调及结果)

任务并行库

Task Parallel Library

任务并行库 (TPL) 是 System.ThreadingSystem.Threading.Tasks 空间中的一组公共类型和 APITPL 的目的是通过简化将并行和并发添加到应用程序的过程来提高开发人员的工作效率。 TPL 动态缩放并发的程度以最有效地使用所有可用的处理器。 此外,TPL 还处理工作分区、ThreadPool 上的线程调度、取消支持、状态管理以及其他低级别的细节操作。 通过使用 TPL,你可以在将精力集中于程序要完成的工作,同时最大程度地提高代码的性能。

C#5的异步编程主要基于TPL,因此可以在适用于异步的地方编写同步形式的代码。地狱回调、事件订阅和错误处理都消失不见,取而代之的是能够清晰表达其意图的异步代码,并且是基于开发者熟悉的结构。

任务是用于实现称之为并发Promise模型的构造。 简单地说,它们“承诺”,会在稍后完成工作,让你使用干净的 APIpromise 协作。

  • Task 表示不返回值的单个操作。
  • Task<T> 表示返回T类型的值的单个操作。

异步函数

C#5引入异步函数(asynchronous function)的概念。通常是指用async修饰符声明的,可包含await表达式的方法或匿名函数。它可以“等待”(await)一个异步操作。这个“等待”看上去非常像一个普通的阻塞调用,剩余的代码在异步操作完成前不会继续执行,但实际上它并没有阻塞当前执行线程。

await运算符

await运算符会暂停对其所属的async方法的求值,直到其操作数表示的异步操作完成。

只能在通过async关键字修饰的方法匿名函数中使用 await 运算符。不能在同步函数的主体、lock 语句块内以及不安全的上下文中使用 await 运算符。

await表达式

Await_expression的操作数称为任务。 它表示一个异步操作,该操作在计算await_expression时可能会也可能不会完成。 Await 运算符的用途是在等待的任务完成之前挂起封闭异步函数的执行,然后获取其结果。

Await 表达式的任务需要是可等待的(awaitable)。

可等待的

这个可等待的其实就是具有一个名为GetAwaiter的可访问实例或扩展方法,方法没有任何参数,返回类型具有以下特点:

  • 实现了System.Runtime.CompilerServices.INotifyCompletion接口(只有一个void OnCompleted (Action continuation);方法)
  • 具有可访问的bool类型的实例属性IsCompleted
  • 具有可访问的实例方法GetResult,无任何参数和类型参数

GetAwaiter 方法的目的是获取任务的awaiter

IsCompleted 属性的目的是确定任务是否已完成。 如果已完成,则无需暂停评估。

INotifyCompletion.OnCompleted 方法的目的是将 "延续" 注册到任务;也就是说,将在任务完成后调用的委托(类型为System.Action)。

GetResult 方法的目的是在任务完成后获取任务的结果。 此结果可能会成功完成(可能包含结果值),也可能是由 GetResult 方法引发的异常。

async/await的核心,其实就是编译器帮我们自动实现的TAP异步方法。编译器在遇到用await修饰的表达式或匿名函数时,帮我们自动生成了一个实现了IAsyncStateMachine接口的状态机(StateMachine),它在内部控制处理了Task的状态。

我们还可以手动实现TAP异步方法,那就是创建一个TaskCompletionSource<T>对象来执行异步操作,并在操作完成时调用SetResultSetExceptionSetCanceled方法。例如:

//同步转异步
public static Task<T> ConvertToAsync<T>(Func<T> func)
{
    var tcs = new TaskCompletionSource<T>();
    tcs.SetResult(func());
    return tcs.Task;
}
//异步转同步
public static T ConvertToSync<T>(Func<Task<T>> taskFunc)
{
    var tcs = new TaskCompletionSource<T>();
    var task = tcs.Task;
    Task.Run(async () =>
    {
        var result = await taskFunc();
        tcs.SetResult(result);
    });
    return task.Result;
}

若还想继续了解更多,可在基于任务的异步模式中再详细了解任务以及任务交互的信息。

最后你可以再看下Stephen Cleary大神写的async/await异步编程中的最佳做法

调用方信息特性

调用方信息特性,可以获取有关方法调用方的信息。可以获取源代码的文件路径、源代码中的行号和调用方的成员名称。 若要获取成员调用方信息,可以使用应用于可选参数的特性。 每个可选参数指定一个默认值。下表列出在 System.Runtime.CompilerServices 命名空间中定义的调用方信息特性:

特性 描述 类型
CallerFilePathAttribute 包含调用方的源文件的完整路径。 完整路径是编译时的路径。 String
CallerLineNumberAttribute 源文件中调用方法的行号。 Integer
CallerMemberNameAttribute 调用方的方法名称或属性名称。 String
public void DoProcessing()
{
    TraceMessage("Something happened.");
}

public void TraceMessage(string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
{
    Trace.WriteLine("message: " + message);
    Trace.WriteLine("member name: " + memberName);
    Trace.WriteLine("source file path: " + sourceFilePath);
    Trace.WriteLine("source line number: " + sourceLineNumber);
}

// Sample Output:
//  message: Something happened.
//  member name: DoProcessing
//  source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
//  source line number: 31

foreach循环中捕获变量的变化

string[] values = { "x", "y", "z" };
var actions = new List<Action>();
foreach (string item in values)
{
    actions.Add(() => Console.WriteLine(item));
}
foreach (Action item in actions)
{
    item();
}

C#3C#4中,以上代码实际上会打印出三个z。问题就在于由lambda表达式捕获的循环变量item,在循环的每次迭代中捕获的变量item的引用都是相同。由于最终变量为z,所以打印了三个z

C#5中,循环的每次迭代都有效的引入一个单独变量。每个委托都捕获并引用不同的变量,这样变量的值就是这次循环迭代中产生的值。

C#5

上一篇:C#4.0


下一篇:c#委托中的匿名方法和lambda表达式