SignalR+Hangfire 實現後臺任務隊列和實時通訊

来源:https://www.cnblogs.com/raok/archive/2023/06/06/17462105.html
-Advertisement-
Play Games

# SignalR+Hangfire 實現後臺任務隊列和實時通訊 1.簡介: SignalR是一個.NET的開源框架,SignalR可使用Web Socket, Server Sent Events 和 Long Polling作為底層傳輸方式實現服務端和客戶端的實時數據交互。 Hangfire是一 ...


SignalR+Hangfire 實現後臺任務隊列和實時通訊

1.簡介:

SignalR是一個.NET的開源框架,SignalR可使用Web Socket, Server Sent Events 和 Long Polling作為底層傳輸方式實現服務端和客戶端的實時數據交互。

Hangfire是一個.NET的開源後臺任務框架 提供統一的編程模型,以可靠的方式處理後臺任務

2.目的:

通過SignalR+Hangfire,我們可以實現一些需要較長時間處理的任務,併在完成及時的通知前端處理結果。

3.以下是我使用SignalR+Hangfire的開發需求:

在net6 webapi的情況下,前端是vue+ts,我現在有個需要就是,我寫了一個介面,是對接stable diffusion webui 文生圖的介面,前端第一個人請求,返回圖沒有問題,
但是,此時在生成圖的過程中,第二個人請求,我希望加入到一個隊列或者別的方式 ,把這個請求放著,我處理完第一個請求之後繼續處理第二個,並且告訴用戶,前面有多少個任務需要等待?

我的開發環境,後端是.net7 前端vue3.0,下麵是對應安裝和使用教程:

1.Hangfire使用

1.安裝nuget包

由於我使用的mysql,對應包為Hangfire.MySqlStorage,大家根據自己的資料庫選擇安裝對應的包

<PackageReference Include="Hangfire" Version="1.8.2" />
<PackageReference Include="Hangfire.MySqlStorage" Version="2.0.3" />

2.添加Hangfire配置

Hangfire的數據是存在資料庫中的,所以在添加配置時候要使用對應的資料庫連接字元串。同時,在UseHangfireServer時,我使用了自定義的隊列名稱,並將同時執行的任務數設置為1,以實現任務隊列中的任務唯一,且任務依次執行。

在program.cs中添加以下配置

1.添加Hangfire

image-20230606215237420

代碼內容:

var connectionString = configuration.GetValue<string>("ConnStr");//資料庫連接配置
// Add Hangfire services.
services.AddHangfire(config =>
{
    config.UseStorage(new MySqlStorage(connectionString, new MySqlStorageOptions
    {
        TablesPrefix = "hangfire_", // 指定表首碼
        PrepareSchemaIfNecessary = true // 允許安裝 MySQL 表格(如果不存在的話)
        // 其他存儲選項
    }));
});

2.應用Hangfire

image-20230606215441914

代碼內容:

// Use Hangfire server and dashboard.
app.UseHangfireServer(new BackgroundJobServerOptions
{
    Queues = new[] { "default", "img-queue" },
    WorkerCount = 1
});
app.UseHangfireDashboard();// 使用 Hangfire 控制面板

3.資料庫配置

配置完成,在使用時,資料庫會生成Hangfire的工作表,如下:

image-20230606220252972

4.Hangfire 控制面板

對應Hangfire 控制面板為 /hangfire

http://localhost:5122/hangfire

1.儀錶盤

image-20230606221156741

2.隊列

image-20230606221240570

5.代碼中的應用

1.發起一個後臺任務

//添加後臺任務
BackgroundJob.Enqueue(() => BackServiceCreateImg(request));

2.後臺任務方法

/// <summary>
/// 後臺任務生成圖片(DisableConcurrentExecution 設置超時時間 Queue設置任務類型)
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 180)]
[Queue("img-queue")]
public async Task BackServiceCreateImg(GraphGenerationRequest request)
{
    //...代碼邏輯省略
}

3.查詢隊列等待任務數

var queueLength = JobStorage.Current.GetMonitoringApi()
                            .EnqueuedCount("img-queue");//指定的隊列類型的隊列等待任務數

2.SignalR使用

1.後端SignalR使用

由於我使用的.net7,微軟自帶SignalR,我們使用時只需要添加引用

using Microsoft.AspNetCore.SignalR;

1.添加SignalR配置

在program.cs中添加以下配置

1.添加SignalR

image-20230606222512852

代碼內容:

// SignalR
services.AddSignalR();

2.配置SignalR hub

image-20230606222625994

代碼內容:

// SignalR hub
app.MapHub<GraphGenerationHub>("/graphhub");

2.創建SignalR hub類

using Hangfire;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.SignalR;

namespace ChatGptWebApi.Hubs
{
    [EnableCors("MyPolicy")]
    public class GraphGenerationHub : Hub
    {

        public GraphGenerationHub()
        {
        }

        public long GetWaitingCount()
        {
            return  JobStorage.Current.GetMonitoringApi()
                .EnqueuedCount("img-queue");
        }
    }
}

3.代碼中的應用

1.依賴註入

通過依賴註入,在要使用的類中註入

image-20230606224441104

private readonly IHubContext<GraphGenerationHub> _hubContext;

4.發送消息

向全體發送

_hubContext.Clients.All.SendAsync("updateWaitingCount", "消息內容.....");

向指定客戶端發送

_hubContext.Clients.Client(request.ConnectionId).SendAsync("updateImgUrl", $"生成圖片失敗:{ex.Message}");

2.前端SignalR使用

前端我用的是VUE+TS

1.安裝SignalR包

通過命令使用 pnpm 安裝 @microsoft/signalr

pnpm install @microsoft/signalr

2.頁面中引用@microsoft/signalr

import * as signalR from "@microsoft/signalr";

3.創建一個useSignalR.ts

創建一個useSignalR.ts來專門處理SignalR消息,然後在需要用到的頁面中引用即可。

代碼內容:

import { onUnmounted, ref } from 'vue';
import { useMessage } from 'naive-ui'
import { HubConnectionBuilder, HubConnection } from '@microsoft/signalr';

export  function useSignalR(
  hubUrl: string,
  hubName: string
) {
  const connection = ref<HubConnection | null>(null);
  const waitingCount = ref(0);
  const imgUrl = ref([]);
  const ms = useMessage();
  const start = async () => {
    if (connection.value && connection.value.state === 'Connected') return;
    connection.value = getConnection(hubUrl);
    if (connection.value) {
     // 連接 SignalR
     connection.value.start()
     .then(() => {
       console.log('SignalR Connected.');
       // 調用 GraphGenerationHub 的 GetWaitingCount 方法獲取隊列等待數
       connection.value?.invoke('GetWaitingCount')
         .then(count => {
           console.log('Waiting Count:', count);
           waitingCount.value = count;
         });
       // 註冊 signalR 接收方法
       connection.value?.on('updateWaitingCount', count => {
         console.log('Waiting Count:', count);
         waitingCount.value = count;
       });
       connection.value?.on('updateImgUrl', newImgUrl => {
        console.log('Waiting imgUrl:', newImgUrl);
        if(typeof newImgUrl === 'string'){
          ms.error(newImgUrl);
        }else{
          ms.success('圖片生成成功。');
        imgUrl.value = newImgUrl;
        }
      });
     })
     .catch(error => {
       console.log('SignalR Connection Error:', error);
     });
    }
  };
  

  const stop = () => {
    connection.value!.stop();
    connection.value = null;
  };

  const getConnection = (
    hubUrl: string
  ): HubConnection => {
    return new HubConnectionBuilder()
      .withUrl(hubUrl)
      .withAutomaticReconnect().build();
  };

  start();

  onUnmounted(() => {
    if (connection.value?.state === 'Connected') connection.value!.stop();
  });

  return {
    connection,
    waitingCount,
    imgUrl,
    start,
    stop
  };
}

4.頁面中的使用

在需要使用signalR的頁面引用useSignalR

 import {useSignalR} from '@/views/chat/hooks/useSignalR';
setup() {
//signalR
const { waitingCount,connection,imgUrl } = useSignalR(apiBaseUrl+'/graphhub');
}

3.案例:SignalR+Hangfire+StableDiffusionAPI 生成圖片

Hangfire實現後臺調用StableDiffusion web介面,然後通過SignalR將結果返回給前端。這樣,對StableDiffusion web的性能要求很低。不會因為生成圖片慢,導致http請求超時的情況。大大改善了前後端交互。

1.前端建立SignalR

入上述頁面中使用介紹的一樣,當添加了

const { waitingCount,connection,imgUrl } = useSignalR(apiBaseUrl+'/graphhub');

打開對應頁面時,就創建了SignalR的連接了。

2.前端發起請求

前端的提交按鈕對應的方法,使用的是axios發送http請求生成圖片。

代碼如下:

const submit = async () => {
        const params = {
          Prompt: description.value,
          connectionId:connection.value?.connectionId //SignalR的客戶端連接ID
        };
      try {
      //signalR
      const response = await axios.post(apiUrl+'/GenerateGraph', params);
      if(response.data.status ==='Fail'){
        ms.error(response.data.message ?? 'error')
      return
      }
      usedCount.value=response.data.data;
      ms.success(response.data.message);

    } catch (error) {
      ms.error('報錯拉!:'+error);
    }
    console.log("提交的參數:", params); // 在控制台輸出提交的參數
  };

3.後端介面和實現

後端介面和實現方法完成定時任務的發起和signalR的消息推送

後端介面如下:

/// <summary>
/// signalR+hangfire生成圖片
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
public async Task<ApiResult<int?>> GenerateGraph(GraphGenerationRequest request)
{
    var res=await _iGptImage.GenerateGraph(request);
    return res;
}

方法實現:

/// <summary>
/// 生成圖片,返回隊列信息和剩餘次數
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<ApiResult<int?>> GenerateGraph(Form.GraphGenerationRequest request)
{
    //添加後臺任務
    BackgroundJob.Enqueue(() => BackServiceCreateImg(request));
    string message = await SendWaitingCount("img-queue");
    return new ApiResult<int?>(HttpResultTypeEnum.Success, count - 1, message);
}
 /// <summary>
/// 推送隊列的等待信息
/// </summary>
/// <param name="enqueue">任務類型</param>
/// <returns></returns>
private async Task<string> SendWaitingCount(string enqueue)
{
    var queueLength = JobStorage.Current.GetMonitoringApi()
        .EnqueuedCount(enqueue);
    string message = $"任務已提交,您前面還有 {queueLength} 個任務正在等待。";
    await _hubContext.Clients.All.SendAsync("updateWaitingCount", queueLength);
    return message;
}

4.案例成果

案例地址(AI聊天+圖片生成):https://ai.terramours.site/

image-20230606232339121

閱讀如遇樣式問題,請前往個人博客瀏覽: https://www.raokun.top
擁抱ChatGPT:https://ai.terramours.site
開源項目地址:https://github.com/firstsaofan/TerraMours

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、開通OpenAI賬號 1.註冊OpenAI賬號 官網地址:https://openai.com/ 註意:提前準備好國外手機號,沒有的話用簡訊平臺購買手機號接收簡訊 2.購買國外手機號 地址:https://tiger-sms.com/ 我用支付寶充值了30多元(起充要30,加上手續費30多有點坑 ...
  • 當《阿裡巴巴Java開發手冊》發佈後,我也是仔細進行了閱讀,想從中找出一些“標準”,讓自己的代碼質量提高。手冊中對 Object 的 equals 方法的使用進行了強制,而且推薦使用 JDK7 中工具類 Objects 的 equals 方法,至此之後我就很少使用 Object.equals() 方... ...
  • # if語句-語法格式 簡單理解if語句之後,我們的if語句語法格式有多種,選擇使用哪種取決於要測試的條件數 # 1.if結構 最簡單的if語句只有一個條件測試和一個代碼塊 其語法格式: ![image](https://img2023.cnblogs.com/blog/3179433/202306 ...
  • #### 使用thymeleaf做html模板,由xhtmlrenderer/flying-saucer-pdf-openpdf將html轉為PDF > LGPL 和 MPL 許可 #### pom.xml引入依賴 ````java org.springframework.boot spring-b ...
  • > 文中所涉及到的代碼運行結果均是在64位機器上執行得到的. ## 基礎知識回顧 在Go中,我們可以使用`unsafe.Sizeof(x)`來查看變數所占的記憶體大小。以下是Go內置的數據類型占用的記憶體大小: | 類型 | 記憶體大小(位元組數) | | : | : | | bool | 1 | | in ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • ## 教程簡介 Ajax即Asynchronous Javascript And XML(非同步JavaScript和XML)在 2005年被Jesse James Garrett提出的新術語,用來描述一種使用現有技術集合的‘新’方法,包括: HTML 或 XHTML, CSS, JavaScript ...
  • ## 前言 本人之前開發了一個叫[電子腦殼](https://github.com/maker-community/ElectronBot.DotNet)的上位機應用,給稚暉君[ElectronBot](https://github.com/peng-zhihui/ElectronBot)開源機器人 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...