原文:异步 OOP 2:构造函数 (stephencleary.com)

异步构造带来了一个有趣的问题。能够在构造函数中使用会很有用,但这意味着构造函数必须返回一个表示将来将构造的值,而不是构造的值。这种概念很难融入现有的语言。awaitTask<T>

底线是不允许构造函数,因此让我们探索一些替代方案。async

工厂模式

构造函数不能,但静态方法可以。使用静态创建方法非常容易,使类型成为自己的工厂:async

public sealed class MyClass
{
private MyData asyncData;
private MyClass() { ... } private async Task<MyClass> InitializeAsync()
{
asyncData = await GetDataAsync();
return this;
} public static Task<MyClass> CreateAsync()
{
var ret = new MyClass();
return ret.InitializeAsync();
}
} public static async Task UseMyClassAsync()
{
MyClass instance = await MyClass.CreateAsync();
...
}

可以完成所有初始化工作,但我更喜欢使用该方法。Createasync InitializeAsync

工厂方法是最常见的异步构造方法,但在某些情况下还有其他方法很有用。

AsyncLazy (for Resources)

如果要创建的实例是共享资源,则可以使用异步延迟初始化来创建共享实例:

private static AsyncLazy<MyResource> resource = new AsyncLazy<MyResource>(async () =>
{
var data = await GetResource();
return new MyResource(data);
}); public static async Task UseResourceAsync()
{
MyResource res = await resource;
}

AsyncLazy<T>非常适合资源;在此示例中,将在第一次编辑时开始构造。任何其他方法,它将绑定到相同的结构,并且当构造完成时,所有服务员都将被释放。施工完成后的任何 s 都会立即继续,因为该值已经可用。resourceawaitawaitawait

如果不将实例用作共享资源,则此方法不起作用。如果实例不是共享资源,则应改用另一种方法。

异步初始化模式

异步构造的最佳方法已经介绍过了:异步工厂方法和 。这些是最好的方法,因为您永远不会公开未初始化的实例。AsyncLazy<T>

但是,有时确实需要构造函数,例如,当其他组件使用反射来创建类型的实例时。这包括数据绑定、IoC 和 DI 框架等。Activator.CreateInstance

在这些情况下,必须返回未初始化的实例,但可以通过应用通用模式来缓解这种情况:每个需要异步初始化的对象都将公开一个包含异步初始化结果的属性。Task Initialization { get; }

模式

如果要将异步初始化视为实现详细信息,可以(可选)为使用异步初始化的类型定义"标记"接口:

/// <summary>
/// Marks a type as requiring asynchronous initialization and provides the result of that initialization.
/// </summary>
public interface IAsyncInitialization
{
/// <summary>
/// The result of the asynchronous initialization of this instance.
/// </summary>
Task Initialization { get; }
}

异步初始化的模式如下所示:

public sealed class MyFundamentalType : IAsyncInitialization
{
public MyFundamentalType()
{
Initialization = InitializeAsync();
} public Task Initialization { get; private set; } private async Task InitializeAsync()
{
// Asynchronously initialize this instance.
await Task.Delay(100);
}
}

这个模式非常简单,但它为我们提供了一些重要的语义:

  • 初始化在构造函数中启动(当我们调用 时)。InitializeAsync
  • 初始化的完成是公开的(通过属性)。Initialization
  • 将从异步初始化引发的任何异常将被捕获并放置在属性上。Initialization

可以(手动)构造此类型的实例,如下所示:

var myInstance = new MyFundamentalType();
// Danger: the instance is not initialized here!
await myInstance.Initialization;
// OK: the instance is initialized now.


使用异步初始化进行组合

很容易创建另一个依赖于此基本类型的类型(即异步组合):

ublic sealed class MyComposedType : IAsyncInitialization
{
private readonly MyFundamentalType _fundamental; public MyComposedType(MyFundamentalType fundamental)
{
_fundamental = fundamental;
Initialization = InitializeAsync();
} public Task Initialization { get; private set; } private async Task InitializeAsync()
{
// Asynchronously wait for the fundamental instance to initialize.
await _fundamental.Initialization; // Do our own initialization (synchronous or asynchronous).
await Task.Delay(100);
}
}

主要区别在于,我们等待所有组件初始化,然后再继续初始化。或者,您可以继续进行一些初始化,并且仅在需要完成这些特定组件时才等待这些组件。但是,每个组件都应在 末尾初始化。InitializeAsync

在撰写时,我们从此模式中获得了一些关键语义:

  • 在组合类型的所有组件的初始化完成之前,其初始化不会完成。
  • 组件初始化产生的任何错误都会通过组合类型显示出来。
  • 组合类型支持异步初始化,并且可以像任何其他支持异步初始化的类型一样依次进行组合。

此外,如果您使用的是"标记"接口,则可以对其进行测试,并异步初始化 IoC/DI 提供给您的实例。这会稍微复杂化您的身份,但允许您将异步初始化视为实现细节。例如,如果 是 的类型:IAsyncInitializationInitializeAsync_fundamentalIMyFundamentalType

private async Task InitializeAsync()
{
// Asynchronously wait for the fundamental instance to initialize if necessary.
var asyncFundamental = _fundamental as IAsyncInitialization;
if (asyncFundamental != null)
await asyncFundamental.Initialization; // Do our own initialization (synchronous or asynchronous).
await Task.Delay(100);
}

顶级处理

我们已经介绍了如何使用异步初始化编写"基本"类型,以及如何通过异步初始化将它们"组合"成其他类型。最终,您将需要使用支持异步初始化的高级类型。

在许多动态创建方案(如 IoC/DI/)中,您只需直接检查并初始化它:Activator.CreateInstanceIAsyncInitialization

object myInstance = ...;
var asyncInstance = myInstance as IAsyncInitialization;
if (asyncInstance != null)
await asyncInstance.Initialization;

但是,如果您通过数据绑定创建类型,或者使用 IoC/DI 将视图模型注入到视图的数据上下文中,则您实际上没有与顶级实例交互的位置。数据绑定将在初始化完成时负责更新 UI,除非初始化失败,因此需要显示失败。遗憾的是,没有实现 ,因此任务完成不会自动显示。您可以在 AsyncEx 库中使用类似 NotifyTaskCompletion 类型的类型来简化此操作:TaskINotifyPropertyChanged

public sealed class MyViewModel : INotifyPropertyChanged, IAsyncInitialization
{
public MyViewModel()
{
InitializationNotifier = NotifyTaskCompletion.Create(InitializeAsync());
} public INotifyTaskCompletion InitializationNotifier { get; private set; }
public Task Initialization { get { return InitializationNotifier.Task; } } private async Task InitializeAsync()
{
await Task.Delay(100); // asynchronous initialization
}
}

数据绑定代码可以使用类似和响应初始化任务完成的路径。InitializationNotifier.IsCompletedInitializationNotifier.ErrorMessage

异步初始化:结论

与异步初始化模式相比,我更喜欢异步工厂方法。异步初始化模式在初始化实例之前公开实例,并且依赖于程序员正确使用 。但在某些情况下,您无法使用异步工厂方法,而异步初始化是一个不错的解决方法。Initialization

不该做什么

下面是一个该执行的操作的示例:

public sealed class MyClass
{
private MyData asyncData;
public MyClass()
{
InitializeAsync();
} // BAD CODE!!
private async void InitializeAsync()
{
asyncData = await GetDataAsync();
}
}

乍一看,这似乎是一个合理的方法:你得到一个启动异步操作的常规构造函数;但是,由于使用了,因此存在一些缺点。async void

第一个问题是,当构造函数完成时,实例仍在异步初始化,并且没有明显的方法来确定异步初始化何时完成。

第二个问题是错误处理:从 引发的任何异常都将直接抛出到构造实例时的当前异常上。异常不会被围绕对象构造的任何子句捕获。大多数应用程序将此视为致命错误。InitializeAsyncSynchronizationContextcatch

本文中的前两个解决方案(异步工厂方法和 )没有这些问题。在异步初始化实例之前,它们不提供实例,并且异常处理更自然。第三种解决方案(异步初始化)确实在初始化之前返回一个实例(我不喜欢),但它通过提供一种标准方法来检测初始化何时完成以及合理的异常处理来缓解这种情况。AsyncLazy<T>

【C#TAP 异步编程】构造函数 OOP的更多相关文章

  1. 【C# TAP 异步编程】三、async\await的运作机理详解

    [原创] 本文只是个人笔记,很多错误,欢迎指出. 环境:vs2022  .net6.0 C#10 参考:https://blog.csdn.net/brook_shi/article/details/ ...

  2. 【C#TAP 异步编程】异步接口 OOP

    在我们深入研究"异步OOP"之前,让我们解决一个相当常见的问题:如何处理异步方法的继承?那么"异步接口"呢? 幸运的是,它确实可以很好地与继承(和接口)一起使用 ...

  3. 【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext

    一.同步上下文(SynchronizationContext)概述 由来 多线程程序在.net框架出现之前就已经存在了.这些程序通常需要一个线程将一个工作单元传递给另一个线程.Windows程序以消息 ...

  4. 【C# TAP 异步编程】二 、await运算符已经可等待类型Awaitable

    await的作用: 1.await是一个标记,告诉编译器生成一个等待器来等待可等待类型实例的运行结果. 2.一个await对应一个等待器 ,任务的等待器类型是TaskAwaiter/TaskAwait ...

  5. 【C# TAP 异步编程】一 、async 修饰符(标记)

    async的作用: 1.async是一个标记,告诉编译器这是一个异步方法. 2.编译器会根据这个标志生成一个异步状态机. 3.编译器将原异步方法中的代码清空,写入状态机的配置,原先异步方法中的代码被封 ...

  6. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  7. 【.NET异步编程系列1】:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

  8. 【异步编程】Part1:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

  9. 异步编程系列06章 以Task为基础的异步模式(TAP)

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

随机推荐

  1. Node内部架构图

    1.Node内部架构图 先来看一下Node节点的内部实现架构图. 首先最上层入口是Restful风格和javaTcp风格的API入口,RestFul请求映射到处理器RestControl.JavaAp ...

  2. javaObject—toString方法

    1 package face_object; 2 /* 3 * Object:所有类的根类. 4 * Object是不断抽取而来的,具备所有对象都具备的共性内容. 5 * 常用的共性功能: 6 * 7 ...

  3. linux关闭防火墙与开启防火墙命令

    一:下面是red hat/CentOs7关闭防火墙的命令! 1:查看防火状态 systemctl status firewalld service iptables status 2:暂时关闭防火墙 ...

  4. python 小兵内置函数进制转换

    Python内置函数进制转换的用法 使用Python内置函数:bin().oct().int().hex()可实现进制转换. 先看Python官方文档中对这几个内置函数的描述: bin(x)Conve ...

  5. String 不可变

    String 源码,String 的修饰符是 final String 采用的是共享模式,被放进常量池 String strA = "abc"; String strB = &qu ...

  6. deque概述

    1.简介 双端队列deque,与vector的最大差异在于: 一.deque运行常数时间对头端或尾端进行元素的插入和删除操作. 二.deque没有所谓的容器概念,因为它是动态地以分段连续空间组合而成随 ...

  7. C++ 子函数参数传递过程

    编译环境:Visual Studio 2015 参数传递与汇编语言有很大关系.子函数传递参数主要方式有三种(这三种参数传递方式都可用用于x86汇编语言甚至其它汇编语言): 寄存器方式传递参数 存储器方 ...

  8. [POI2010]TEL-Teleportation

    因为题目中要求 \(1 \sim 2\) 的最短路只有 \(5\),于是我们可以考虑直接使用人脑将图分层. 那么我们怎么定义每层的点呢?因为要使 \(1 \sim 2\) 的最短路只有 \(5\),我 ...

  9. listview界面显示

    1.布局写listview      2.找到listview           3.封装新闻数据到list集合中 ,目的是为adapter提供数据展示.     4.封装一个Adapter类继承B ...

  10. 大前端JS篇之搞懂【Set】

    我认为前端生态很大,犹如一片汪洋大海,很难短时间内窥其全貌,在这里我们不谈其他,只聊聊 Set Set是 es6 新提出的一个对象,也是一种数据结构,为什么es6要提出这样一个新对象呢,无非就是丰富j ...