场景

  生产者和消费者队列, 生产者有多个, 消费者也有多个, 生产到消费需要异步.

下面用一个Asp.NetCore Web-API项目来模拟

  创建两个API, 一个Get(), 一个Set(), Get返回一个字符串, Set放入一个字符串, Get返回的就是Set进去的字符串.

  实现如下:  

[Route("api/[controller]/[action]")]
public class FooController : Control
{
IMessageQueue _mq;
public FooController(IMessageQueue mq)
{
_mq = mq;
} [HttpGet]
public string Get()
{
string str = _mq.ReadOne<string>();
return str;
} [HttpGet]
public void Set(string v)
{
_mq.WriteOne(v);
}
} public interface IMessageQueue
{
T ReadOne<T>();
void WriteOne<T>(T value);
} public class MessageQueue: IMessageQueue
{
private object _value; public T ReadOne<T>()
{
return (T)_value;
} public void WriteOne<T>(T value)
{
_value = value; }
}

接着在StartUp中把IMessageQueue给注入了.

services.AddSingleton<IMessageQueue, MessageQueue>();

运行后, 先调用/api/foo/set/?v=xxx, 再调用/api/foo/get/

可以看到成功返回了xxx

第二步, value字段改为队列:

使set进去的值不会被下一个覆盖, get取队列最前的值

为了线程安全, 这里使用了ConcurrentQueue<T>

代码如下:

public class MessageQueue: IMessageQueue
{
private readonly ConcurrentQueue<object> _queue = new ConcurrentQueue<object>(); public T ReadOne<T>()
{
_queue.TryDequeue(out object str);
return (T)str ;
} public void WriteOne<T>(Tvalue)
{
_queue.Enqueue(value);
}
}

那么此时, 只要get不断地轮询, 就可以取到set生产出来的数据了.

调用/api/foo/set/

三, 异步阻塞

再增加需求, 调换get和set的顺序,先get后set模拟异步, (我这里的demo是个web-api会有http请求超时之类的...假装不存在)我想要get调用等待有数据时才返回.

也就是说我想要在浏览器地址栏输入http://localhost:5000/api/foo/get/之后会不断地转圈直到我用set接口放入一个值

方案A: while(true), 根本无情简直无敌, 死等Read() != null时break; 为防单核满转加个Thread.Sleep();

方案B: Monitor, 一个Wait()一个Exit/Release();

但是以上两个方案都是基于Thread的, .Net4.0之后伴随ConcurrentQueue一起来的还有个BlockingCollection<T>相当好用

方案C: 修改后代码如下:

public class MessageQueue : IMessageQueue
{
private readonly BlockingCollection<object> _queue = new BlockingCollection<object>(new ConcurrentQueue<object>()); public T ReadOne<T>()
{
var obj = _queue.Take();
return (T)obj;
} public void WriteOne<T>(T value)
{
_queue.Add(value);
}
}

此时, 如果先get, 会阻塞等待set; 如果已经有set过数据就会直接返回队列中的数据. get不会无功而返了. 基于这个类型, 可以实现更像样的订阅模型.

扩展RPC

这里的set是生产者, get是消费者, 那如果我的这个生产者并不单纯产生数据返回void而是需要等待一个结果的呢? 此时订阅模型不够用了, 我需要一个异步的RPC .

比如有个Ask请求会携带参数发起请求, 并等待, 知道另外有个地方处理了这个任务产生结果, ask结束等待返回这个结果answer.

我可以回头继续用方案A或B, 但连.net4.0都已经过去很久了, 所以应该用更好的基于Task的异步方案.

代码如下, 首先新增两个接口:

public interface IMessageQueue
{
void Respond<TRequest, TResponse>(Func<TRequest, TResponse> func);
Task<TResponse> Rpc<TRequest, TResponse>
(TRequest req); T ReadOne<T>();
void WriteOne<T>(T data);
}

接着定义一个特殊的任务类:

public class RpcTask<TRequest, TResponse>
{
public TaskCompletionSource<TResponse> Tcs { get; set; }
public TRequest Request { get; set; }
}

实现刚才新加的两个接口:

public Task<TResponse> Rpc<TRequest, TResponse>(TRequest req)
{
TaskCompletionSource<TResponse> tcs = new TaskCompletionSource<TResponse>();
_queue.Add(new RpcTask<TRequest, TResponse> { Request = req, Tcs = tcs});
return tcs.Task;
} public void Respond<TRequest, TResponse>(Func<TRequest, TResponse> func)
{
var obj = _queue.Take();
if(obj is RpcTask<TRequest, TResponse> t)
{
var response = func(t.Request);
t.Tcs.SetResult(response);
}
}

同样的, 写两个Web API接口, 一个请求等待结果 一个负责处理工作

[HttpGet]
public async Task<string> Ask(string v)
{
var response = await _mq.Rpc<MyRequest, MyResponse>(new MyRequest { Id = v });
return $"[{response.DoneTime}] {response.Id}";
} [HttpGet]
public void Answer()
{
_mq.Respond<MyRequest, MyResponse>((req)=> new MyResponse { Id = req.Id, DoneTime = DateTime.Now });
}

上面还随便写了两个class作为请求和返回

public class MyRequest
{
public string Id { get; set; }
}
public class MyResponse
{
public string Id { get; set; }
public DateTime DoneTime { get; set; }
}

测试一下, 用浏览器或postman打开三个选项卡, 各发起一个Ask接口的请求, 参数v分别为1 2 3, 三个选项卡都开始转圈等待

然后再打开一个选项卡访问answer接口, 处理刚才放进队列的任务, 发起一次之前的三个选项卡之中就有一个停止等待并显示返回数据. 需求实现.

这里用到的关键类型是TaskCompletionSource<T>.

再扩展

如果是个分布式系统, 请求和处理逻辑不是在一个程序里呢? 那么这个队列可能也是一个单独的服务. 此时就要再加个返回队列了, 给队列中传输的每一个任务打上Id, 返回队列中取出返回之后再找到Id对于的TCS.SetResult()

  

C#异步案例一则的更多相关文章

  1. nodejs异步案例

    const fs = require('fs'); fs.readFile('./test.txt', 'utf-8', (err, data) => { err ? console.error ...

  2. 深度学习_1_Tensorflow_2_数据_文件读取

    tensorflow 数据读取 队列和线程 文件读取, 图片处理 问题:大文件读取,读取速度, 在tensorflow中真正的多线程 子线程读取数据 向队列放数据(如每次100个),主线程学习,不用全 ...

  3. 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul

    本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...

  4. 058.Python前端Django与Ajax

    一 Ajax简介 AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步Javascript和XML".即使用Javascript语言与服务 ...

  5. Ajax与ashx异步请求的简单案例

    Ajax与ashx异步请求的简单案例: 前台页面(aspx): <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//E ...

  6. 一个完整的springmvc + ajaxfileupload实现图片异步上传的案例

    一,原理 详细原理请看这篇文章 springmvc + ajaxfileupload解决ajax不能异步上传图片的问题.java.lang.ClassCastException: org.apache ...

  7. spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,guava限流,定时任务案例, 发邮件

    本文介绍spring boot集成swagger,自定义注解,拦截器,xss过滤,异步调用,定时任务案例 集成swagger--对于做前后端分离的项目,后端只需要提供接口访问,swagger提供了接口 ...

  8. vue路由异步组件案例

    最近研究了vue性能优化,涉及到vue异步组件.一番研究得出如下的解决方案. 原理:利用webpack对代码进行分割是异步调用组件前提.异步组件在优先级上让位同步组件.下面介绍的是怎么实现异步组件. ...

  9. PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)

    源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkP ...

随机推荐

  1. vue路由安装

    1.安装路由: vue ui cnpm install vue-router 2.使用,导入: 默认创建项目的时候就已经帮你写好了. import router from "vue-rout ...

  2. dbms_job基础

    a.创建job: dbms_job.submit(jobno,what,next_date,interval);b.删除job: dbms_job.remove(jobno); c.修改要执行的操作: ...

  3. 定制linux镜像并自动化安装

    最近碰到个需求:要在内网环境安装centos6.5系统并搭建服务,但由于自动部署脚本里安装依赖包使用的是yum安装,而服务器无法连接外网,实施人员也不会本地yum源搭建….. 本来想法是打算把需要的依 ...

  4. [考试反思]1105csp-s模拟测试102: 贪婪

    还是有点蠢... 多测没清空T3挂40...(只得了人口普查分20) 多测题要把样例复制粘两遍自测一下防止未清空出锅. 然而不算分... 其实到现在了算不算也不重要了吧... 而且其实T3只考虑最长路 ...

  5. NOIP模拟测试11

    这次考试T1想到了正解没有去实现,然后就死了,不过我估计就算想到正解也会挂(26^2和暴力一个分),肝了两个小时T2屁都没蹦出来,T3没有搞清那个式子的含义. (不过一分没挂) T1:string 开 ...

  6. 使用Typescript重构axios(三十)——添加axios.getUri方法

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  7. 线段树合并学习笔记(P4556)

    直入主题: 学习线段树合并..... 从名字就能看出,这个东西要合并线段树..... 线段树怎么能合并呢...... 暴力合就行了啊...... 一次从上往下的遍历,把所有的节点信息暴力合并,然后就没 ...

  8. csp-s 66

    我向来只在考砸的时候写博客.这次题很水,但是我极没有状态,我T1没看题目前面的话: 不知道这个条件的我蒙蔽的答题.推各种柿子,想这个矩阵的特殊构造,就是同行的构造,然后我T1想了1个多小时,然后死了! ...

  9. java中线程同步的几种方法

    1.使用synchronized关键字 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchro ...

  10. 打包上传被拒 Guideline 2.5.1 - Performance - Software Requirements

    打包上传被拒 Guideline 2.5.1 - Performance - Software Requirements 在项目中全部搜索:prefs:root 找到后,把这个私有的 NSURL *u ...