簡體   English   中英

在C#中執行並發任務

[英]Performing concurrent tasks in C#

我有一項服務,需要盡快讀取來自Amazon SQS的消息。 我們期望流量很大,我希望能夠每秒讀取1萬條以上的消息。 不幸的是,我目前大約每秒10條消息。 顯然,我有工作要做。

這就是我正在使用的(轉換為控制台應用程序以簡化測試):

private static int _concurrentRequests;
private static int _maxConcurrentRequests;

public static void Main(string[] args) {
    _concurrentRequests = 0;
    _maxConcurrentRequests = 100;

    var timer = new Timer();
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    timer.Interval = 10;
    timer.Enabled = true;

    Console.ReadLine();
    timer.Dispose();
}

public static void OnTimedEvent(object s, ElapsedEventArgs e) {
    if (_concurrentRequests < _maxConcurrentRequests) {
        _concurrentRequests++;
        ProcessMessages();
    }
}

public static async Task ProcessMessages() {
    var manager = new MessageManager();
    manager.ProcessMessages();  // this is an async method that reads in the messages from SQS

    _concurrentRequests--;
}

我沒有收到接近100個並發請求,而且似乎也不是每10毫秒觸發一次OnTimedEvent

我不確定Timer是否是正確的方法。 我對這種編碼沒有太多的經驗。 我樂於嘗試任何事情。

更新

多虧了calebboyd,我才更接近實現自己的目標。 這是一些非常糟糕的代碼:

private static SemaphoreSlim _locker;

public static void Main(string[] args) {
    _manager = new MessageManager();

    RunBatchProcessingForeverAsync();
}
private static async Task RunBatchProcessingForeverAsync() {
    _locker = new SemaphoreSlim(10, 10);
    while (true) {
        Thread thread = new Thread(new ParameterizedThreadStart(Process));
        thread.Start();
    }
}

private static async void Process(object args) {
    _locker.WaitAsync();
    try {
        await _manager.ProcessMessages();
    }
    finally {
        _locker.Release();
    }

}

這樣我可以接近每秒讀取大量消息,但是問題是我的ProcessMessages調用永遠不會完成(或者可能會在很長一段時間后完成)。 我想我可能需要限制我一次運行的線程數。

關於如何改進此代碼,以便使ProcessMessages有機會完成的任何建議?

因為未等待MessageManager對象上的ProcessMessages方法,所以我將假定它綁定到在其中執行的同一線程。僅將函數標記為async不會將工作傳遞給新線程。 在這種假設下,該代碼實際上並未在多個線程中執行。 您可以使用以下代碼在更多線程池中執行代碼。

管理器對象可能無法處理並發使用。 因此,我在Task.Run lambda中創建了它。 這也可能是昂貴的,因此是不切實際的。

async Task RunBatchProcessingForeverAsync () {
    var lock = new SemaphoreSlim(initialCount: 10);
    while (true) {
        await lock.WaitAsync();
        Task.Run(() => {
            try {
                var manager = new MessageManager();
                manager.ProcessMessages();
            } finally {
                lock.Release();
            }
        });
    }
}

我已經有一段時間沒有寫C#了,但這應該可以同時,重復,永久地運行您的方法10次。

如@calebboyd所建議,首先必須使線程異步。 現在,如果您去這里- 調用API時在何處使用並發性 ,您將看到一個異步線程足以用於快速地池化網絡資源。 如果您能夠在一個請求中從亞馬遜獲取多條消息,那么您的生產者線程(對亞馬遜進行異步調用的那個)就可以了-每秒可以發送數百個請求。 這不會成為您的瓶頸。 但是,處理接收到的數據的繼續任務將被移交給線程池。 在這里,您有機會遇到瓶頸-假設每秒有100條響應到達,每個響應包含100條消息(達到您的10K msgs / sec近似值)。 每秒鍾您有100個新任務,每個任務都需要您的線程來處理100條消息。 現在有兩個選項:(1)這些消息的處理不受CPU限制-您只需將它們發送到DB,或者(2)執行消耗CPU的計算,例如科學計算,序列化或某些繁重的業務邏輯。 如果您的情況是(1),則將瓶頸向后推向DB。 如果為(2),則您別無選擇,只能放大/縮小或優化計算。 但是您的瓶頸可能不是生產線程-如果實現正確(請參閱上面的鏈接以獲取示例)。

我假設異步方法在線程池中排隊,該線程池的線程數與可用處理器的數量相同。 您可能會生成100個請求,但它們仍由8個線程執行。 嘗試創建N個線程的數組並將其使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM