zoukankan      html  css  js  c++  java
  • C# 中使用 Task 实现提前加载

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

    场景

    在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) ,则会造成死锁。

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

    一个神奇的异步转同步的方式

    有如下一个 GetDataAsync 方法,使用了 async/await ,所以调用方也必须使用 async/await 的方式,不然就失去了同步的特性。
    问题是,有时候 async/await 引起的病毒传播所带来的改动,可能会很大,尤其对于旧代码。

    如何实现一种诡异的调用方法,来调用这种方法呢?

    public class Work
    {
        public async Task<bool> GetDataAsync()
        {
            return await Task.Run(() =>
            {
                return false;
            });
        }
    } 
    

    诡异代码如下:

    public class Work
    {
        public bool GetData()
        {
            Task<bool> getDataTask = CreateGetDataTask();
            return getDataTask.Result;
        }
    
        // private 
        private async Task<bool> CreateGetDataTask()
        {
            return await Task.Run(async () =>
            {
                return await GetAvaiableAsync();
            }).ConfigureAwait(false);  // 必须写 ConfigureAwait(false) 
    
            // 使用下面这段代码会死锁
            // return await GetAvaiableAsync().ConfigureAwait(false);
        }
    
    }
    

    这样,外部就可以调用 GetData 这个很普通的方法,而不用调用 GetDataAsync 这个方法了。

    其实就是上面提到的实现方法二,
    但这里需要注意的是,GetData 方法会引发线程阻塞,如果在UI线程调用,则会卡UI,非特殊情况,不建议这么使用。
    即使使用,也不要将 GetData 作为一般方法对外公开。

    关于异步转同步,可参见:
    将 async/await 异步代码转换为安全的不会死锁的同步代码(使用 PushFrame) - walterlv


    补充:
    其实使用 TaskCompletionSource 可以实现一样的效果,当然,一样需要注意使用 ConfigureAwait(false),不然也会带来死锁问题。


    原文链接:https://www.cnblogs.com/jasongrass/p/10645566.html

    END

  • 相关阅读:
    mass Framework draggable插件
    将一段数字从右到左每隔三位插入一个逗号
    Firefox 12正式发布
    各大瀑布流简析与建议
    判定是否为非负整数
    mass Framework droppable插件
    HTML 5 <video> 标签
    SQL DELETE 语句
    SQL CREATE TABLE 语句(转)
    HTML <fieldset> 标签
  • 原文地址:https://www.cnblogs.com/jasongrass/p/10645566.html
Copyright © 2011-2022 走看看