场景

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

下面用一个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. unityevent与持续按键触发

    上一篇中提到一种鼠标按下时的事件触发,即采用eventtrigger设定pointerdown和pointerup并绑定相应事件.但是若要实现持续按键则需要对绑定的每个方法都添加实现持续按键方法.所以 ...

  2. Spring IoC的概念

    Spring IoC的基础知识 Spring 框架可以说是Java世界中最成功的框架,它的成功来自于理念,而不是技术,它最核心的理念是IoC(控制反转)和AOP(面向切面编程),其中IoC是Sprin ...

  3. 2018.8.3 python中的set集合及深浅拷贝

    一.字符串和列表的相互转化 之前写到想把xx类型的数据转化成yy类型的数据,直接yy(xx)就可以了,但是字符串和列表的转化比较特殊,相互之间的转化要通过join()和split()来实现. 例如: ...

  4. 省市区三级联动(vue)

    vue项目中使用到三级联动,现在自己实现一个三级联动,仅供大家参考一下,直接上代码. <template> <section class="container"& ...

  5. 根据多个成对的cron表达式生成的时间段,合并

    场景:数据库一张表,有个startcron 和endcron 两个字段,根据表达式计算今天的所有时间段. 例:startcron :0 30 20 ? * * endcron :0 30 21 ? * ...

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

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

  7. 原生JS实现二叉搜索树(Binary Search Tree)

    1.简述 二叉搜索树树(Binary Search Tree),它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子 ...

  8. MySQL原生PHP操作-天龙八步

    <?php //1.第一步[建立连接] $conn = mysqli_connect('localhost','root','123456') or die('数据库连接失败!'); //2.第 ...

  9. etcd-operator快速入门完全教程

    Operator是指一类基于Kubernetes自定义资源对象(CRD)和控制器(Controller)的云原生拓展服务,其中CRD定义了每个operator所创建和管理的自定义资源对象,Contro ...

  10. Matlab 文件格式化/Matlab Source File Formator

    由于需要使用到别人编写的Matlab代码文件,但是呢不同的人有不同的风格,有的写得就比较糟糕了. 为了更好地理解代码的内容,一个比较美观的代码会让人身心愉悦. 但是在网上并没有找到一个比较好的实现,此 ...