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
}
这里适用的场景,需要是 EarlierWork
比 DoSomething
先执行一段时间,而且在 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 实现提前加载的更多相关文章
- Java 构造函数(抽象类中的构造函数) 和 加载
博客分类: 面向对象设计的原则 与 概念 1. Java 的构造函数 与初始化块: a. 抽象类的构造函数 若果在父类中(也就是抽象类)中显示的写了有参数的构造函数,在子类是就必须写一个构造函数来 ...
- Spring提前加载与懒加载
首先,Spring默认是提前加载,这意味着当项目启动,spring初始化,spring会把所有的扫描包下的 ,所有带spring 注解(@Component.@Repository.@Service. ...
- sqlalchemy 实体属性提前加载
在flask里需要给视图传送数据,肯定需要把模型的实体属性提前加载,可以使用 sqlalchemy.orm.subqueryload 或 sqlalchemy.orm.joinedload 示例: @ ...
- EXT中的iconCls 图标加载
刚刚遇到了个奇怪的问题. 我用 在主页面用TAB autoLoad:{url:link, nocache: true, scripts:true} 加载页面Student.jsp, 郁闷,FF可以正常 ...
- java_有返回值线程_提前加载例子
package com.demo.test3; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionE ...
- MVC中实现部分内容异步加载
MVC中实现部分内容异步加载 action中定义一个得到结果集的方法 public ActionResult GetItemTree(string title, int itemid, int? pa ...
- 在前端页面对easyui中的datagrid与jqgrid加载后的数据进行操作
因为项目的需求,需要在grid中加载数据后再在前端页面执行操作,所以在easyui中的grid与jqgrid都进行了测试和操作: eayui中grid数据的操作: //构造集合对象 var list ...
- 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 3
备注: 因为文章太长,所以将它分为三部分,本文是第三部分. 第一部分:深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1 第二部分:深入浅出经典面试题:从浏览器中输入URL ...
- 从浏览器中输入URL到页面加载的发生了什么-转载
转:https://www.cnblogs.com/confach/p/10050013.html 背景 “从浏览器中输入URL到页面加载的发生了什么“,这是一道经典的面试题,涉及到的知识面非常多,但 ...
随机推荐
- angular4 get,post请求(带参数,与不带参数)
一:在app.module.ts引入HttpMoudle import { BrowserModule } from '@angular/platform-browser'; import { Htt ...
- 【leetcode 简单】 第七十四题 缺失数字
给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数. 示例 1: 输入: [3,0,1] 输出: 2 示例 2: 输入: [9,6,4,2 ...
- socket系统调用
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { int retval; struct socket *sock; in ...
- oracle11g创建修改删除表
oracle11g创建修改删除表 我的数据库名字: ORCL 密码:123456 1.模式 2.创建表 3.表约束 4.修改表 5.删除表 1.模式 set oracle_sid=OR ...
- 读书笔记 effective c++ Item 52 如果你实现了placement new,你也要实现placement delete
1. 调用普通版本的operator new抛出异常会发生什么? Placement new和placement delete不是C++动物园中最常遇到的猛兽,所以你不用担心你对它们不熟悉.当你像下面 ...
- Python爬取微信好友
前言 今天看到一篇好玩的文章,可以实现微信的内容爬取和聊天机器人的制作,所以尝试着实现一遍,本文记录了实现过程和一些探索的内容 来源: 痴海 链接: https://mp.weixin.qq.com/ ...
- GO里的“指针”
指针 *T即为类型T的指针 &t即为获取变量t的地址 *p即为获取指针变量所指向的内容 var p *int 指针的*在左边 类型在右边.这里的 *int就是一个指针类型. 跟int str ...
- 无需编译app切换线上、测试环境
在咱们测试过程中,经常需要切换测试环境和线上环境.大致有如下几个方案. 一.服务器地址编译到app中 此种方式需要在代码里保存两套配置,一套指向线上,一套指向测试.通过编译参数分别生成测试包.线上包. ...
- Codeforces 429B Working out(递推DP)
题目链接:http://codeforces.com/problemset/problem/429/B 题目大意:两个人(假设为A,B),打算健身,有N行M列个房间,每个房间能消耗Map[i][j]的 ...
- P2184 【贪婪大陆】
看到全是线段树或者树状数组写法,就来提供一发全网唯一cdq分治三维偏序解法吧 容易发现,这个题的查询就是对于每个区间l,r,查询有多少个修改区间li,ri与l,r有交集 转化为数学语言,就是查询满足l ...