原文:异步 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. unity3d之public变量引发错误

    public变量引发错误 在vs ide中怎么更改也无效 后来发现public里面的值一直不改变,手动改之.

  2. VUE3 之 组件间事件通信 - 这个系列的教程通俗易懂,适合新手

    1. 概述 相关定律告诉我们:这个世界上的任何事物之间都会存在一定联系,"城门失火,殃及池鱼"就是一个很好的例子.因此如果我们能够尽早发现这些看不见的联系,就能很好的解决更多遇见的 ...

  3. 004 Linux 揭开神器 vim 面纱

    01 开篇初识 vim vim 功能吊炸天,但我们掌握一些常用的命令即可应对日常的使用了,不记流水账! Linux 中最常用的编辑器是什么? vim ! vi 跟 vim 啥区别? vim 就是 vi ...

  4. Gc如何判断对象可以被回收?

    Gc如何判断对象可以被回收? 1 引用计数器 引用计数法的算法思路:给对象增加一个引用计数器,每当对象增加一个引用计数器+1,失去一个引用-1,所以当计数器是0的时候对象就没有引用了,就会被认为可回收 ...

  5. py调用shell

    py调用shell

  6. JavaScript数据结构之链表

    链表相较于数组的优缺点 1. 链表在 插入.删除.移动数据效率比数组要高,数组插入.移动.删除数据需要改变没有数据的索引,而链表则只需要更改指针即可 2. 在查询方面,数组要优于链表,数组存储的数据是 ...

  7. [学习笔记]Linux环境下部署 .Net5 程序

    ​公司的项目需要部署到一台公网的linux服务器,以便同事们测试小程序. 目标服务器是新搭建的CentOS 8虚拟机,以非docker的方式部署.现记录过程便于日后部署至项目甲方的服务器上,因为甲方的 ...

  8. centOS 强制卸载PHP

    centOS上的php过低是需要重新安装时,不得不卸载自定义安装,如下操作 查看php版本命令: #php -v 这个命令是删除不干净的 #yum remove php 因为使用这个命令以后再用 #p ...

  9. python中生成器的两段代码

    生产者-消费者经典单线程问题 import time def consumer(name):     print("%s 准备吃包子啦!" %name)     while Tru ...

  10. 加密模块hashlib+日志模块logging

    目录 1.hashlib 加密模块 1.hashlib模块基本使用 1.2 详细操作 ①md5加密模式 ②sha256复杂加密模式 ③加盐操作(普通加盐) ④加盐操作(动态加盐) 2.logging ...