原文:异步 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. nodejs express异常捕获

    参考链接: http://blog.coinidea.com/web开发/nodejs-1131.html 由于nodejs是非阻塞单进程单线程的,一旦nodejs抛出异常,整个服务就会停掉.服务将会 ...

  2. ansible command和shell的区别

    1.command模块不支持管道符和变量等,如果要使用这些,需要shell模块. 2.在使用ansible中的时候,默认的模块是-m command,从而模块的参数不需要填写,直接使用即可

  3. mysql新增用户无法登陆问题解决ERROR 1045 (28000)

    mysql增加新用户无法登陆解决方法 ERROR 1045 (28000): Access denied for user 'appadmin'@'localhost' (using password ...

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

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

  5. CF Round #669 Div2

    A 可以发现不论往怎样一个串往后加上两个 \(0\) 或两个 \(1\) 其奇数位和偶数位上的差值都是相同的.因此我们两位两位考虑这个 \(01\) 串,对于相邻两位相同那么直接留下,否则留下 \(0 ...

  6. Web3对于我们普通人意味着什么?

    ▲ 点击101链视界,关注不走丢 大家好,我是阿创,这是我的第27篇原创文章. 上一篇文章中我们了解了互联网的前世今生:Web 1.0.2.0.3.0,我们对Web的前两个阶段都不陌生,特别是 Web ...

  7. 详解git fetch与git pull的区别(实操)

    感谢原文作者:R-H-R 原文链接:https://blog.csdn.net/riddle1981/article/details/74938111 git fetch和git pull都可以将远端 ...

  8. VC 创建快捷方式

    转载请注明来源:https://www.cnblogs.com/hookjc/ VC6下测试时使用的是绝对地址BOOL CFGDlg::CreateLink (        LPSTR szPath ...

  9. 编写PHP扩展

    转载请注明来源:https://www.cnblogs.com/hookjc/ PHP 5.2 环境的扩展(PHP Extension) 需求:比如开发一个叫做 heiyeluren  的扩展,扩展里 ...

  10. imagenamed和imageWithContentOfFile的区别

    @implementation ViewController /** 图片的两种加载方式: 1> imageNamed: a. 就算指向它的指针被销毁,该资源也不会被从内存中干掉 b. 放到As ...