介绍一种/两种可以提前做点什么事情的方法。

场景

在UI线程中执行耗时操作,如读取大文件,为了不造成UI卡顿,常采用异步加载的方式,即 async/await 。

通常的写法是这样的:

private async Task DoSomething()
{
    // init work
    await Task.Run(()=>
    {
        // IO          
    });
    // after work 
}

问题与需求

这里虽然解决了UI卡顿的问题,但需要得到最终结果(即 after work 中的代码执行),仍然需要等待。
在部分场景中,如果可以提前执行耗时代码,则可以减少等待时间。

设想的代码类似与这样:


private void EarlierWork()
{
    // 提前执行耗时操作,如从一个大文件中查找数据。
}

priavte void DoSomething()
{
    // init
    // 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
    // after work 
}

这里适用的场景,需要是 EarlierWorkDoSomething 先执行一段时间,而且在 EasilerWork 执行时,就已经可以获取相关的数据,不缺失必要的参数。(这样的场景……额,可能不并不多)

实现

这里的实现有两种方式,都是基于 C# 的 async/await 异步模型。

实现一: awaiter

using System.Runtime.CompilerServices;  
using System.Threading.Tasks;

private TaskAwaiter<string> _getStringDataAwaiter;

private void EarlierWork()
{
    // 提前执行耗时操作,如从一个大文件中查找数据。
    _getStringDataAwaiter = Task.Run(()=>
    {
        // 耗时操作
        return "Data";
    }).GetAwaiter();
}

private async Task DoSomething()
{
    // init
    // 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
    string data = await Task.Run(()=>_getStringDataAwaiter.GetResult());
    // after work 
}

注意,这里需要使用 Task.Run() 的方式调用获取结果的 GetResult 方法,否则会是调用线程卡顿,即,如果调用线程是UI,则会造成UI卡顿。

需要注意的是,TaskAwaiter 这个API是提供给编译器使用的,不建议在生产环境中使用

The System.Runtime.CompilerServices namespace provides functionality for compiler writers who use managed code to specify attributes in metadata that affect the run-time behavior of the common language runtime.


This API supports the product infrastructure and is not intended to be used directly from your code.
TaskAwaiter Struct (System.Runtime.CompilerServices) | Microsoft Docs

实现二:

推荐的实现方式。

using System.Threading.Tasks;

private Task<string> _getStringDataTask

private void EarlierWork()
{
    // 提前执行耗时操作,如从一个大文件中查找数据。
    _getStringDataTask = CreateGetDataTask();    
}

private async Task<string> CreateGetDataTask()
{
    return await Task.Run(()=>
    {
        // 耗时操作
        return "Data";
    }).ConfigureAwait(false);  // 必须写 ConfigureAwait(false) 

    // 此处的代码将不会返回原线程执行;不过这里一般不写代码。
}

private void DoSomething()
{
    // init
    // 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
    string data = _getStringDataTask.Result;
    // after work 
}

这种实现方式需要注意死锁问题,如果不使用 ConfigureAwait(false) ,则会造成死锁。

关于死锁的更多内容,可以看这里:


END


本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/%E4%BD%BF%E7%94%A8-Task-%E5%AE%9E%E7%8E%B0%E6%8F%90%E5%89%8D%E5%8A%A0%E8%BD%BD.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 刘俊杰 (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系