簡體   English   中英

使用C#的新異步功能等待網絡數據包的最佳方法是什么

[英]What is the best way to wait on a network packet using C#'s new async feature

我最近一直在玩新的Async CTP,我遇到過一種我不確定如何繼續的情況。

在我目前的代碼庫中,我使用的是“作業”和“作業管理器”的概念。 作業僅用於處理初始消息,發送響應,然后等待響應。

我已經有了基於同步套接字的現有代碼,其中網絡線程正在等待數據到達,然后將其傳遞給事件處理程序,最終傳遞給作業管理器。

作業管理器查找將處理消息的作業,並將其傳遞。

所以場景是這樣的:

  1. 工作經理獲得新消息並啟動工作。
  2. 作業啟動,處理消息並發送回復消息。
  3. 此時,作業將等待對回復的響應。

這是一個偽代碼示例:

class MyJob : Job
{
    public override void RunJob( IPacketMsg packet )
    {
        // handle packet

        var myReply = new Packet();
        SendReply( myReply );

        await GetResponse();
    }
}

但我不完全確定如何在第3步繼續。工作經理將獲得響應,然后將其交給正在運行的工作。 但我不知道如何讓工作等待回應。

我考慮過創建一個等待在WaitHandle上阻塞的任務,但這是最好的解決方案嗎?

在這種情況下我還能做些什么嗎?

編輯關於Async CTP的主題,在未使用UI的情況下會發生什么。 我已經閱讀了Eric Lippert的Async博客,但我不相信它曾經觸及過如何在沒有UI線程的情況下在后台工作的主題(它是關閉背景工作者還是......?)

  1. 工作經理獲得新消息並啟動工作。
  2. 作業啟動,處理消息並發送回復消息。
  3. 此時,作業將等待對回復的響應。

首先,我應該提到Async CTP非常好地處理異步操作 ,但異步事件並沒有那么多。 您可能想要考慮基於Rx的方法。 但是讓我們繼續使用Async CTP。

您有兩個基本選項來創建任務:

  • 有代表。 例如, Task.Factory.StartNew將在線程池上運行委托。 自定義任務工廠和調度程序為任務委托提供了更多選項(例如,指定委托必須在STA線程上運行)。
  • 沒有代表。 例如, TaskFactory.FromAsync包裝現有的Begin / End方法對, TaskEx.FromResult返回“future constant”, TaskCompletionSource可用於顯式控制TaskFromAsyncFromResult都在內部使用TCS )。

如果作業處理受CPU約束,則將其傳遞給Task.Factory.StartNew 我將假設作業處理受CPU限制。

工作管理員偽代碼:

// Responds to a new message by starting a new job on the thread pool.
private void RespondToNewMessage(IPacketMsg message)
{
  IJob job = ..;
  Task.Factory.StartNew(job.RunJob(message));
}

// Holds tasks waiting for a response.
private ConcurrentDictionary<int, TaskCompletionSource<IResponse>> responseTasks = ..;

// Asynchronously gets a response for the specified reply.
public Task<IResponse> GetResponseForReplyAsync(int replyId)
{
  var tcs = new TaskCompletionSource<IResponse>();
  responseTasks.Add(replyId, tcs);
  return tcs.Task;
}

// Responds to a new response by completing and removing its task.
private void RespondToResponse(IResponse response)
{
  var tcs = responseTasks[response.ReplyId];
  responseTasks.Remove(response.ReplyId);
  tcs.TrySetComplete(response);
}

這個想法是,工作經理還管理一系列優秀的回復。 為了實現這一點,我引入了一個簡單的int reply標識符,作業管理器可以使用該標識符來確定響應哪個響應。

現在工作可以像這樣工作:

public override void RunJob(IPacketMsg packet)
{
  // handle packet
  var myReply = new Packet();
  var response = jobManager.GetResponseForReplyAsync(myReply.ReplyId);
  SendReply(myReply);

  await response;
}

因為我們將作業放在線程池線程上,所以有一些棘手的事情:

  1. 必須在發送回復之前調用 GetResponseForReplyAsync (注冊任務),然后稍后await 這是為了避免在我們有機會注冊之前發送回復和收到回復的情況。
  2. RespondToResponse將在完成任務之前刪除任務注冊,以防萬一完成任務導致另一個回復以相同的id發送。

如果作業足夠短,不需要將它們放在線程池線程上,那么可以簡化解決方案。

關於Async CTP的主題,在沒有使用UI的情況下會發生什么。 我已經閱讀了Eric Lippert的Async博客,但我不相信它曾經觸及過如何在沒有UI線程的情況下在后台工作的主題(它是關閉背景工作者還是......?)

await將返回其執行上下文。 在UI過程中,這是一個UI消息循環。 在ASP.NET中,這是ASP.NET線程池。 在其他情況下(控制台應用程序和Win32服務),沒有上下文,因此continuation排隊到ThreadPool 這通常不是所希望的行為,因此我編寫了一個可以在這些情況下使用的AsyncContext類。

不使用BackgroundWorker 在像你這樣的服務器端場景中,根本沒有后台線程並不罕見。

您只需使用await模式連接事件處理程序的其余部分,如下所示:

 public async void RunJob(IPacketMsg msg)
 {
     // Do Stuff

     var response = await GetResponse();

     // response is "string", not "Task<string>"

     // Do More Stuff
 }

 public Task<string> GetResponse()
 {
     return Task.Factory.StartNew(() =>
        {
             _networkThingy.WaitForDataAvailable();

             return _networkThingy.ResponseString;
        });
 }

當get響應任務完成時,方法的其余部分將在當前同步上下文中執行。 但是,在此之前,您的方法執行已經產生(因此,在GetResponse中啟動的任務完成之前,等待之后的任何代碼都不會運行)

暫無
暫無

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

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