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

场景

在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

C# 中使用 Task 实现提前加载的更多相关文章

  1. Java 构造函数(抽象类中的构造函数) 和 加载

    博客分类: 面向对象设计的原则 与 概念   1. Java 的构造函数 与初始化块: a. 抽象类的构造函数 若果在父类中(也就是抽象类)中显示的写了有参数的构造函数,在子类是就必须写一个构造函数来 ...

  2. Spring提前加载与懒加载

    首先,Spring默认是提前加载,这意味着当项目启动,spring初始化,spring会把所有的扫描包下的 ,所有带spring 注解(@Component.@Repository.@Service. ...

  3. sqlalchemy 实体属性提前加载

    在flask里需要给视图传送数据,肯定需要把模型的实体属性提前加载,可以使用 sqlalchemy.orm.subqueryload 或 sqlalchemy.orm.joinedload 示例: @ ...

  4. EXT中的iconCls 图标加载

    刚刚遇到了个奇怪的问题. 我用 在主页面用TAB autoLoad:{url:link, nocache: true, scripts:true} 加载页面Student.jsp, 郁闷,FF可以正常 ...

  5. java_有返回值线程_提前加载例子

    package com.demo.test3; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionE ...

  6. MVC中实现部分内容异步加载

    MVC中实现部分内容异步加载 action中定义一个得到结果集的方法 public ActionResult GetItemTree(string title, int itemid, int? pa ...

  7. 在前端页面对easyui中的datagrid与jqgrid加载后的数据进行操作

    因为项目的需求,需要在grid中加载数据后再在前端页面执行操作,所以在easyui中的grid与jqgrid都进行了测试和操作: eayui中grid数据的操作: //构造集合对象 var list ...

  8. 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 3

    备注: 因为文章太长,所以将它分为三部分,本文是第三部分. 第一部分:深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1 第二部分:深入浅出经典面试题:从浏览器中输入URL ...

  9. 从浏览器中输入URL到页面加载的发生了什么-转载

    转:https://www.cnblogs.com/confach/p/10050013.html 背景 “从浏览器中输入URL到页面加载的发生了什么“,这是一道经典的面试题,涉及到的知识面非常多,但 ...

随机推荐

  1. Spyder简述

    导言 想打造轮子, 就必须要有一套完善的造轮子的工具. 我在jupyter+sciTE的组合里转来转去, 最后还是打算放弃这个组合, 因为离开了自动完成/调用提示/随时随地的访问文档帮助, 前行之路太 ...

  2. 【实操笔记】MySQL主从同步功能实现

    写在前边: 这两天来了个需求,配置部署两台服务器的MySQL数据同步,折腾了两天查了很多相关资料,一直连不上,后来发现其实是数据库授权的ip有问题,我们用的服务器是机房中的虚拟机加上反向代理出来的,坑 ...

  3. HDU 5532 Almost Sorted Array (最长非递减子序列)

    题目链接 Problem Description We are all familiar with sorting algorithms: quick sort, merge sort, heap s ...

  4. 关于Python编码问题小记

    Python编码问题小记: 引子: 最近在复习redis,当我在获取redis的key的时候,redis 存储英文和汉字下面这个样子的,我知道汉字是用16进制的UTF-8编码了,然后突然很想搞清楚字符 ...

  5. RTSP消息交互过程

    c表示客户端,s表示RTSP服务器端 第一步:查询服务器可用方法 1 c---s :OPTION request //查询s有哪些方法可用 s---c:OPTION response //s回应信息的 ...

  6. 利用rundll32执行程序的函数执行程序

    1.前言 无意间发现hexacorn这个国外大佬,给出了很多通过rundll32执行DLL中的函数执行程序的方法,思路很灵巧. 2.原理 rundll32加载dll 用法: rundll32 < ...

  7. Win10搜索不能用

    使用win10进行搜索时,一直显示win10特色的滚动条,但就是检索不出东西,我的主要是检索不到windows的内容: (个人感觉使用win10检索网页内容不太专业,就关闭了Web搜索) 最后有发现网 ...

  8. html5拖拽初窥

    说到拖动,大概有两种方式,一种是js实现,之前已经介绍过,今天来讲解另外一种方式,那就是使用html5实现拖动. css样式 .box { width: 200px; height: 200px; b ...

  9. Java基础83 JSP标签及jsp自定义标签(网页知识)

    1.JSP标签 替代jsp脚本,用于jsp中执行java代码1.1.内置标签:  <jsp:forward></jsp:forward>  相当于:request.getReu ...

  10. 洛谷P2422 良好的感觉

    题目意思就是:最大化一个区间的和与这个区间的最小值的乘积. 换一个角度看问题,如果我们穷举一个最小值 $ a_i $ ,然后往左右扩展,显然是对的,复杂度 $ O(n^2) $.所以我们要优化一下这个 ...