原文:异步 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. vue学习15-自定义组件model使用

    <!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta http ...

  2. eclipse不能创建web项目,如何设置(亲测可用)

    具体描述:就是在项目右键或者file-->new的时候没有dynamic web project选项)(我这里已经解决.所以看得到) 根本原因:就是有没有web的开发插件 两种办法 1.下载使用 ...

  3. [JavaWeb]利用JSP的编码特性制作免杀后门

    利用JSP的编码特性制作免杀后门 这里是借鉴了Y4stacker师傅的thinkings 待解决的问题 JSP解析 JSP"乱码"为什么还能被识别 "乱码"的J ...

  4. Python 使用 Windows10 桌面通知

    前言 Win10 没有提供简单命令行方式来触发桌面通知,所以使用 Python 来写通知脚本. 一番搜索,找到 win10toast .但这开源仓库已无人维护,通过 github fork 的关系图, ...

  5. 使用 MVVM Toolkit Source Generators

    关于 MVVM Toolkit 最近 .NET Community Toolkit 发布了 8.0.0 preview1,它包含了从 Windows Community Toolkit 迁移过来的以下 ...

  6. python20day

    昨日回顾 正则表达式 元字符 量词 贪婪非贪婪 转义符 re模块 findall 会优先显示分组内容 取消优先显示(?: ) search 只能返回第一个符合条件的项 得到的结果需要.group()取 ...

  7. python网络爬虫-动态网页抓取(五)

    动态抓取的实例 在开始爬虫之前,我们需要了解一下Ajax(异步请求).它的价值在于在与后台进行少量的数据交换就可以使网页实现异步更新. 如果使用Ajax加载的动态网页抓取,有两种方法: 通过浏览器审查 ...

  8. 新年好 takoyaki,期待再次与你相见

    一.序 今天是中国农历一年的最后一天,往年都叫年三十,今年没有三十,最后一天是二十九.厨房的柴火味.窗外的鞭炮声还有不远处传来的说笑声,一切都是熟悉味道,新年到了,家乡热闹起来了.平常左邻右舍都是看不 ...

  9. 利用 Python 进行数据分析(Python 数据分析)· 第 2 版

    译者:SeanCheney 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. ApacheCN 机器学习交流群 629470233 ApacheCN 学习资源 Sklearn 与 ...

  10. 什么是Native方法 (转)

    一个Native Method就是一个java调用非java代码的接口(NDK也跟这有关吗?(疑问)一个Native Method由非java语言实现 在定义一个native method时,并不提供 ...