场景

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

下面用一个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. Spring Boot 快速入门 史上最简单

    1.Spring Boot 概述 Spring Boot 是所有基于 Spring 开发的项目的起点.Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的 ...

  2. PHP获取当前时间

    PHP获取系统当前时间,有date()可以使用. 但date()当前系统时间是格林威治时间,比我们所在的时区晚了整整8个小时.以前处理这个问题时,只是简单的把获取的当前系统的时间戳加上8个小时的时间, ...

  3. 关于 typeof 的暂时性死区,了解一下

    将知识转化为能力,核心是掌握20%行业核心技能,把学习培养成习惯,持续深耕,用能力解决问题,方能持续成长!那么基础好,就是必须条件. 最近看 数据类型,知道数据类型判断有三种方式,typeof 是其中 ...

  4. 学习笔记22_AspMvc简介

    *Mvc和webForm区别 1. Mvc模式下,前台和后台的交流,是后台提供数据,使用对象包裹的形式,前台来使用,类似于webForm定义一个属性那样. 2.Mvc模式下,再也不是使用this.la ...

  5. Netty连接处理那些事

    编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo.Rocketmq.Hadoop等,针对高性能RPC,一般都是基 ...

  6. vue自定义长按指令

    1.前言 在word中,当我们需要删除一大段文本的时候,我们按一下键盘上的退格键,就会删除一个字,当我们长按住退格键时,就会连续不停的删除,这就是键盘按键的长按功能.那么我们也想在网页中让一个按钮也具 ...

  7. ssm整合的登录

    新建一个web工程,主要结构如下: 数据库创建如下: 控制层的代码FormController 类 package codeRose.controller; import org.springfram ...

  8. Scrapy进阶知识点总结(六)——中间件详解

    概述 查看scrapy官网的框架图,可以看出中间件处于几大主要组件之间,类似于生产流水线上的加工过程,将原料按照不同需求与功能加工成成品 其中4,5处于下载器与引擎之间的就是下载中间件,而spider ...

  9. SpringBoot之微服务日志链路追踪

    SpringBoot之微服务日志链路追踪 简介 在微服务里,业务出现问题或者程序出的任何问题,都少不了查看日志,一般我们使用 ELK 相关的日志收集工具,服务多的情况下,业务问题也是有些难以排查,只能 ...

  10. 【转载】常见十大经典排序算法及C语言实现【附动图图解】

    原文链接:https://www.cnblogs.com/onepixel/p/7674659.html 注意: 原文中的算法实现都是基于JS,本文全部修改为C实现,并且统一排序接口,另外增加了一些描 ...