簡體   English   中英

如何處理第三方代碼中的死鎖

[英]How to handle a deadlock in third-party code

我們有一個第三方方法Foo ,它有時會因為未知原因而陷入死鎖。

我們正在執行一個單線程 tcp-server 並每 30 秒調用一次此方法以檢查外部系統是否可用。

為了緩解第三方代碼中的死鎖問題,我們將 ping 調用放在Task.Run中,以便服務器不會死鎖。

喜歡

async Task<bool> WrappedFoo()
{
    var timeout = 10000; 

    var task = Task.Run(() => ThirdPartyCode.Foo());
    var delay = Task.Delay(timeout);

    if (delay == await Task.WhenAny(delay, task ))
    {
        return false;
    }
    else
    {
        return await task ;
    }
}

但這(在我們看來)有可能使自由線程的應用程序匱乏。 因為如果調用ThirdPartyCode.Foo死鎖,線程將永遠無法從死鎖中恢復,如果這種情況經常發生,我們可能會耗盡資源。

是否有一種通用方法應該如何處理死鎖的第三方代碼?

CancellationToken不起作用,因為第三方 api 不提供任何取消選項。

更新:手頭的方法來自 SAP 提供的 SAPNCO.dll,用於建立和測試與 sap 系統的 rfc 連接,因此該方法不是簡單的網絡 ping。 我重命名了問題中的方法以避免進一步的誤解

是否有一種通用方法應該如何處理死鎖的第三方代碼?

是的,但這並不容易或簡單。

行為不端的代碼的問題在於它不僅會泄漏資源(例如,線程),而且還可以無限期地持有重要資源(例如,一些內部“句柄”或“鎖”)。

強制回收線程和其他資源的唯一方法是結束進程。 該操作系統用於清理行為不端的進程,並且非常擅長。 因此,這里的解決方案是啟動一個子進程來執行 API 調用。 您的主應用程序可以通過重定向 stdin/stdout 與其子進程通信,如果子進程超時,主應用程序可以終止它並重新啟動它。

不幸的是,這是取消不可取消代碼的唯一可靠方法。

您的代碼沒有取消被阻止的操作。 使用 CancellationTokenSource 並將取消令牌傳遞給Task.Run

var cts=new CancellationTokenSource(timeout);

try
{
    await Task.Run(() => ThirdPartyCode.Ping(),cts.Token);
    return true;
}
catch(TaskCancelledException)
{
    return false;
}

阻塞很可能是由於網絡或 DNS 問題引起的,而不是實際的死鎖。

這仍然浪費了等待網絡操作完成的線程。 您可以使用 .NET 自己的Ping.SendPingAsync異步 ping指定超時:

var ping=new Ping();

var reply=await ping.SendPingAsync(ip,timeout);
return reply.Status==IPStatus.Success;

PingReply class 包含比簡單的成功/失敗更詳細的信息。 Status 屬性單獨區分路由問題、無法到達的目的地、超時等

取消任務是一種協作操作,您將CancellationToken傳遞給所需的方法,並在外部使用CancellationTokenSource.Cancel

public void Caller()
{
     try
     {
          CancellationTokenSource cts=new CancellationTokenSource();
          Task longRunning= Task.Run(()=>CancellableThirdParty(cts.Token),cts.Token);
          Thread.Sleep(3000); //or condition /signal
          cts.Cancel();
     }catch(OperationCancelledException ex)
     {
          //treat somehow
     }
    
}
public void CancellableThirdParty(CancellationToken token)
{
    while(true)
    {
        // token.ThrowIfCancellationRequested()  -- if you  don't treat the cancellation here
        if(token.IsCancellationRequested)
        {
           // code to treat the cancellation signal
           //throw new OperationCancelledException($"[Reason]");
        }
    }
}

正如您在上面的代碼中看到的,為了取消正在進行的任務,其中運行的方法必須圍繞CancellationToken.IsCancellationRequested標志或簡單的CancellationToken.ThrowIfCancellationRequested方法構建,以便調用者只需發出CancellationTokenSource.Cancel

不幸的是,如果第三方代碼不是圍繞CancellationToken設計的(它不接受CancellationToken參數),那么您無能為力。

暫無
暫無

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

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