实用技巧

>>返回《C# 并发编程》

  • 1. 初始化共享资源
  • 2. Rx延迟求值
  • 3. 异步数据绑定
  • 4. 异步构造
  • 5. 异步属性

1. 初始化共享资源

不管同时有多少线程调用 GetSharedIntegerAsync ,这个工厂委托只会运行一次,并且所有线程都等待同一个实例。

  • 实例在创建后会被缓存起来,以后所有对 Value 属性的访问都返回同一个实例。


public static void UtilShareRun()
{
    // 示例1: 100次并行调用,只输出一次,验证了 只被执行一次 和 线程安全性
    Parallel.For(0, 100, (i, s) =>
    {
        UtilShare share = new UtilShare();
        share.GetSharedIntegerAsync().Wait();
    });
    // 示例2: 显示出调度线程号的切换情况
    // 示例3: 执行前已经调用了 share.GetSharedIntegerAsync() 
    // 那么后面无论是否设置 ConfigureAwait 后面是不会发生上下文切换的,因为已经是直接拿到结果了
    // share.GetSharedIntegerAsync().Wait();
    // AsyncContext.Run(async () =>
    // {
    //     UtilShare share = new UtilShare();
    //     System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] before.");
    //     await share.GetSharedIntegerAsync()
    //       //.ConfigureAwait(false);
    //       ;
    //     System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] after.");
    // });
    
}
public class UtilShare
{
    static int _simpleValue;
    static readonly Lazy> MySharedAsyncInteger = new Lazy>(async () =>
    {
        System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}]");
        await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
                // 只输出一次
                System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] " + nameof(MySharedAsyncInteger));
        return _simpleValue++;
    });
    public async Task GetSharedIntegerAsync()
    {
        int sharedValue = await MySharedAsyncInteger.Value;
    }
}

示例1 输出:

; 使用当前上下文调用
[1]
; 因为设置了 ConfigureAwait 导致上下文不延续,后面交给线程池线程执行
[18] MySharedAsyncInteger

示例2 输出:

[1] before.
[1]
[4] MySharedAsyncInteger
; 因为 await share.GetSharedIntegerAsync();延续了上下文
; 所以此处恢复了调用前是一个上下文
; 如果设置为不延续,则此处线程号会是线程池线程
[1] after.

示例3 输出:

; 第一次执行
[1]
[4] MySharedAsyncInteger
; 因为已经有结果了,后面不会造成上下文切换
[1] before.
[1] after.

本例中委托返回一个 Task 对象,就是一个用异步方式得到的整数值。

  • 不管有多少代码段同时调用 ValueTask 对象只会创建一次,并且每个调用都返回同一个对象
  • 每个调用者可以用 await 调用这个 Task 对象,(异步地)等待它完成

Lazy 委托中的代码会在当前同步上下文中运行。

如果有几种不同类型的线程会调用 Value(例如一个 UI 线程和一个线程池线程,或者两个不同的 ASP.NET 请求线程),那最好让委托只在线程池线程中运行。这实现起来很简单,只要把工厂委托封装在 Task.Run 调用中:

public static void UtilShareTaskRun()
{
    Parallel.For(0, 100, (i, s) =>
    {
        UtilShareTask share = new UtilShareTask();
        share.GetSharedIntegerAsync().Wait();
    });
}
public class UtilShareTask
{
    static int _simpleValue;
    static readonly Lazy> MySharedAsyncInteger = new Lazy>(() =>
        Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(2));
            // 只输出一次
            System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] " + nameof(MySharedAsyncInteger));
            return _simpleValue++;
        })
    );
    public async Task GetSharedIntegerAsync()
    {
        int sharedValue = await MySharedAsyncInteger.Value;
    }
}

输出:

[19] MySharedAsyncInteger

2. Rx延迟求值

想要在每次被订阅时就创建一个新的源 observable 对象

  • 例如让每个订阅代表一个不同的 Web 服务请求。

Rx 库有一个操作符Observable.Defer (初始化时会执行委托)

  • 每次 observable 对象被订阅时,它就会执行一个委托。
  • 该委托相当于是一个创建 observable 对象的工厂
public static void UtilDeferRun()
{
    var invokeServerObservable = Observable.Defer(() => GetValueAsync().ToObservable());
    invokeServerObservable.Subscribe(_ => { });
    // invokeServerObservable.Subscribe(_ => { });
    Thread.Sleep(2000);
}
static async Task GetValueAsync()
{
    Console.WriteLine("Calling server...");
    await Task.Delay(TimeSpan.FromMilliseconds(100));
    Console.WriteLine("Returning result...");
    return 13;
}

输出:

Calling server...
Returning result...

注意: 如果对 Defer 后的 observable 对象 await 或者 Wait() 也会被触发订阅。

3. 异步数据绑定

在异步地检索数据时,需要对结果进行数据绑定(例如绑定到 Model-View-ViewModel 设计模式中的 ViewModel)。

可以使用 AsyncEx 库中的 NotifyTaskCompletion 类:

class MyViewModel
{
    public MyViewModel()
    {
        MyValue = NotifyTaskCompletion.Create(CalculateMyValueAsync());
    }
    public INotifyTaskCompletion MyValue { get; private set; }
    private async Task CalculateMyValueAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(10));
        return 13;
    }
}

可以绑定到 INotifyTaskCompletion 属性中的各种属性,如下所示:


    

也可以自己编写数据绑定的封装类代替 AsyncEx 库中的类。下面的代码介绍了基本思路:

class BindableTask : INotifyPropertyChanged
{
    private readonly Task _task; 
    
    public BindableTask(Task task)
    {
        _task = task;
        var _ = WatchTaskAsync();
    }
    private async Task WatchTaskAsync()
    {
        try
        {
            await _task;
        }
        catch { }
        OnPropertyChanged("IsNotCompleted");
        OnPropertyChanged("IsSuccessfullyCompleted");
        OnPropertyChanged("IsFaulted");
        OnPropertyChanged("Result");
    }
    public bool IsNotCompleted
    {
        get
        {
            return !_task.IsCompleted;
        }
    }
    public bool IsSuccessfullyCompleted
    {
        get
        {
            return _task.Status == TaskStatus.RanToCompletion;
        }
    }
    public bool IsFaulted { get { return _task.IsFaulted; } }
    public T Result
    {
        get
        {
            return IsSuccessfullyCompleted ? _task.Result : default(T);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

4. 异步构造

异步初始化模式

public static void AsyncConstructionRun()
{
    var task = Task.Run(async () =>
    {
        IMyFundamentalType instance = new MyFundamentalType();
        System.Console.WriteLine("Instance created.");
        var instanceAsyncInit = instance as IAsyncInitialization;
        if (instanceAsyncInit != null)
        {
            await instanceAsyncInit.Initialization;
            System.Console.WriteLine("Instance Initialized.");
        }
    });
    task.Wait();
}

interface IMyFundamentalType { }

interface IAsyncInitialization
{
    Task Initialization { get; }
}

class MyFundamentalType : IMyFundamentalType, IAsyncInitialization
{
    public MyFundamentalType()
    {
        Initialization = InitializeAsync();
    }

    public Task Initialization { get; private set; }

    private async Task InitializeAsync()
    {
        System.Console.WriteLine("MyFundamentalType initializing.");
        // 对这个实例进行异步初始化。
        await Task.Delay(TimeSpan.FromSeconds(1));
        System.Console.WriteLine("MyFundamentalType initialized.");
    }
}

输出:

MyFundamentalType initializing.
Instance created.
MyFundamentalType initialized.
Instance Initialized.

可以对这种模式进行扩展,将类和异步初始化结合起来。下面的例子定义了另一个类,它以前面建立的 IMyFundamentalType 为基础:

public static void AsyncConstructionsRun()
{
    AsyncInitialization.WhenAllInitializedAsync(new MyComposedType(new MyFundamentalType()), new MyComposedType(new MyFundamentalType())).Wait();
}

class MyComposedType : IAsyncInitialization
{
    private readonly IMyFundamentalType _fundamental;
    public MyComposedType(IMyFundamentalType fundamental)
    {
        _fundamental = fundamental;
        Initialization = InitializeAsync();
    }
    public Task Initialization { get; private set; }
    private async Task InitializeAsync()
    {
        System.Console.WriteLine("MyComposedType initializing.");
        // 如有必要,异步地等待基础实例的初始化。
        var fundamentalAsyncInit = _fundamental as IAsyncInitialization;
        if (fundamentalAsyncInit != null)
            await fundamentalAsyncInit.Initialization;
        // 做自己的初始化工作(同步或异步)。...
        System.Console.WriteLine("MyComposedType initialized.");
    }
}


public static class AsyncInitialization
{
    public static Task WhenAllInitializedAsync(params object[] instances)
    {
        return Task.WhenAll(instances.OfType().Select(x => x.Initialization));
    }
}

输出:

MyFundamentalType initializing.
MyComposedType initializing.
MyFundamentalType initializing.
MyComposedType initializing.
MyFundamentalType initialized.
MyComposedType initialized.
MyFundamentalType initialized.
MyComposedType initialized.

5. 异步属性

如果每次访问属性都会启动一次新的异步操作,那说明这个“属性”其实应该是一个方法。

public static void UtilPropRun()
{
    var instance = new AsyncProp();
    var task = Task.Run(async () =>
    {
        var propValue = await instance.Data.Task;
        System.Console.WriteLine($"PropValue:{propValue}");
    });
    task.Wait();
}

class AsyncProp
{
    // 作为一个缓存的数据。
    public AsyncLazy Data { get { return _data; } }
    private readonly AsyncLazy _data = new AsyncLazy(async () =>
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        return 13;
    });
}

输出:

PropValue:13

尽量不要用 ResultWait 把异步代码强制转换为同步代码。

你可能感兴趣的:(实用技巧)