Skip to main content
 首页 » 编程设计

c#之异步 Web 服务调用不一致异步

2024年02月25日32kuangbin

我在使用带有 ASP.NET Web API 2 的任务并行库创建异步 Web 服务时遇到问题。我对方法 StartAsyncTest 进行了异步调用,并创建了一个取消标记以中止方法。我全局存储 token ,然后检索它并从第二个方法 CancelAsyncTest 调用它。这是代码:

// Private Global Dictionary to hold text search tokens 
private static Dictionary<string, CancellationTokenSource> TextSearchLookup 
    = new Dictionary<string, CancellationTokenSource>(); 
 
/// <summary> 
/// Performs an asynchronous test using a Cancellation Token 
/// </summary> 
[Route("StartAsyncTest")] 
[HttpGet] 
public async Task<WsResult<long>> StartAsyncTest(string sSearchId) 
{ 
    Log.Debug("Method: StartAsyncTest; ID: " + sSearchId + "; Message: Entering..."); 
 
    WsResult<long> rWsResult = new WsResult<long> 
    { 
        Records = -1 
    }; 
 
    try 
    { 
        var rCancellationTokenSource = new CancellationTokenSource(); 
        { 
            var rCancellationToken = rCancellationTokenSource.Token; 
 
            // Set token right away in TextSearchLookup 
            TextSearchLookup.Add("SyncTest-" + sSearchId, rCancellationTokenSource); 
 
            HttpContext.Current.Session["SyncTest-" + sSearchId] = 
                rCancellationTokenSource; 
 
            try 
            { 
                // Start a New Task which has the ability to be cancelled  
                var rHttpContext = (HttpContext)HttpContext.Current; 
 
                await Task.Factory.StartNew(() => 
                { 
                    HttpContext.Current = rHttpContext; 
 
                    int? nCurrentId = Task.CurrentId; 
 
                    StartSyncTest(sSearchId, rCancellationToken); 
 
                }, TaskCreationOptions.LongRunning); 
            } 
            catch (OperationCanceledException e) 
            { 
                Log.Debug("Method: StartAsyncText; ID: " + sSearchId 
                    + "; Message: Cancelled!"); 
            } 
        } 
    } 
    catch (Exception ex) 
    { 
        rWsResult.Result = "ERROR"; 
        if (string.IsNullOrEmpty(ex.Message) == false) 
        { 
            rWsResult.Message = ex.Message; 
        } 
    } 
 
    // Remove token from Dictionary 
    TextSearchLookup.Remove(sSearchId); 
    HttpContext.Current.Session[sSearchId] = null; 
    return rWsResult; 
} 
 
private void StartSyncTest(string sSearchId, CancellationToken rCancellationToken) 
{ 
    // Spin for 1100 seconds 
    for (var i = 0; i < 1100; i++) 
    { 
        if (rCancellationToken.IsCancellationRequested) 
        { 
            rCancellationToken.ThrowIfCancellationRequested(); 
        } 
 
        Log.Debug("Method: StartSyncTest; ID: " + sSearchId 
            + "; Message: Wait Pass #" + i + ";"); 
 
        Thread.Sleep(1000); 
    } 
 
    TextSearchLookup.Remove("SyncTest-" + sSearchId); 
 
    HttpContext.Current.Session.Remove("SyncTest-" + sSearchId); 
} 
 
[Route("CancelAsyncTest")] 
[HttpGet] 
public WsResult<bool> CancelAsyncTest(string sSearchId) 
{ 
    Log.Debug("Method: CancelAsyncTest; ID: " + sSearchId 
        + "; Message: Cancelling..."); 
 
    WsResult<bool> rWsResult = new WsResult<bool> 
    { 
        Records = false 
    }; 
 
    CancellationTokenSource rCancellationTokenSource = 
        (CancellationTokenSource)HttpContext.Current.Session["SyncTest-" + sSearchId]; 
 
    // Session doesn't always persist values. Use TextSearchLookup as backup 
    if (rCancellationTokenSource == null) 
    { 
        rCancellationTokenSource = TextSearchLookup["SyncTest-" + sSearchId]; 
    } 
 
    if (rCancellationTokenSource != null) 
    { 
        rCancellationTokenSource.Cancel(); 
 
        TextSearchLookup.Remove("SyncTest-" + sSearchId); 
        HttpContext.Current.Session.Remove("SyncTest-" + sSearchId); 
 
        rWsResult.Result = "OK"; 
        rWsResult.Message = "Cancel delivered successfully!"; 
    } 
    else 
    { 
        rWsResult.Result = "ERROR"; 
        rWsResult.Message = "Reference unavailable to cancel task" 
            + " (if it is still running)"; 
    } 
 
    return rWsResult; 
} 

在我将它部署到 IIS 之后,我第一次调用 StartAsyncTest 然后调用 CancelAsyncTest(通过 REST 端点)时,两个请求都会通过并按预期取消。但是,第二次,CancelAsyncTest 请求挂起,并且仅在 StartAsyncTest 完成后(1100 秒后)才调用该方法。我不知道为什么会这样。 StartAsyncTest 似乎在调用一次后劫持所有线程。我感谢任何人可以提供的任何帮助!

请您参考如下方法:

I store the token globally and then retrieve it and call it from a second method CancelAsyncTest.

这可能不是一个好主意。您可以“全局”存储这些 token ,但这只是“全局”到单个服务器。一旦第二台服务器进入画面,这种方法就会失效。

也就是说,HttpContext.Current永远不应该分配给。这很可能是您所看到的奇怪行为的原因。此外,如果您的实际代码比 ThrowIfCancellationRequested 更复杂- 即,如果它实际上正在收听 CancellationToken - 然后调用Cancel可以执行 StartSyncTest 的剩余部分从 调用 Cancel ,这将导致对 HttpContext.Current 的值产生相当大的混淆。 .

总结:

  • 我建议完全取消这种方法;它在网络农场上根本不起作用。相反,将您的“任务状态”保存在数据库等外部存储系统中。
  • 不要通过 HttpContext跨线程。