【发布时间】:2019-11-22 16:45:04
【问题描述】:
我在使用带有 ASP.NET Web API 2 的任务并行库创建异步 Web 服务时遇到问题。我对方法 StartAsyncTest 进行了异步调用,并创建了一个取消令牌以中止该方法。我全局存储令牌,然后检索它并从第二种方法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/O 之类的东西?
-
另外,虽然我不能说这是一个明确的答案,但如果将 HttpContext 传递给不同的线程,您可能会出错 - 您只是使用它来跟踪线程吗?使用并发字典来跟踪它。
-
每个请求是否都有唯一的
sSearchId?不然会出问题。您可能会放弃Session的用法,因为我怀疑 CTS 是否会按照通常的方式正确序列化/反序列化。另外,将同步等待更改为异步;您可以将整个Task.Factory.StartNew...替换为简单的await Task.Delay(TimeSpan.FromSeconds(1100.0), rCancellationToken);,如果CTS 被取消,则会抛出TaskCanceledException。 -
是的,真正的用例(这只是一个显示问题的示例)是在文本文件中搜索字符串,这会占用大量处理器资源。
HttpContext被传递给线程,因为调用Task.Factory.StartNew不会持久化它。此外,每个sSearchId都是独一无二的 -
关于将取消源保存在静态变量中的
Dictionary,请看一下:Lifetime of ASP.NET Static Variable
标签: c# asp.net-web-api2 task-parallel-library