异步 Web 服务调用不一致异步
Async Web Service call not consistently asynchronous
我在使用带有 ASP.NET Web API 的任务并行库创建异步 Web 服务时遇到问题 2. 我对方法 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 store the token globally and then retrieve it and call it from a second method CancelAsyncTest.
这可能不是个好主意。您可以将这些令牌 "globally" 存储到单个服务器,但那只是 "global"。一旦第二台服务器进入画面,这种方法就会失效。
也就是说,HttpContext.Current
永远不应该分配给。这很可能是您所看到的奇怪行为的原因。此外,如果您的实际代码比 ThrowIfCancellationRequested
更复杂 - 即,如果它实际上是 监听 CancellationToken
- 那么对 Cancel
的调用可以在 调用 Cancel
中从 执行 StartSyncTest
的剩余部分,这将导致对 HttpContext.Current
.
的值的相当大的混淆
总结一下:
- 我建议完全放弃这种方法;它在网络农场上根本不起作用。相反,将 "task state" 保存在外部存储系统中,例如数据库。
- 不要跨线程传递
HttpContext
。
一位同事提供了对 Task.Factory.StartNew
的替代调用(在 StartAsyncTest
内):
await Task.Factory.StartNew(() =>
{
StartSyncTest(sSearchId, rCancellationToken);
},
rCancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext());
这个实现似乎解决了异步问题。现在以后对 CancelAsyncTest
的调用会成功并按预期取消任务。
我在使用带有 ASP.NET Web API 的任务并行库创建异步 Web 服务时遇到问题 2. 我对方法 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 store the token globally and then retrieve it and call it from a second method CancelAsyncTest.
这可能不是个好主意。您可以将这些令牌 "globally" 存储到单个服务器,但那只是 "global"。一旦第二台服务器进入画面,这种方法就会失效。
也就是说,HttpContext.Current
永远不应该分配给。这很可能是您所看到的奇怪行为的原因。此外,如果您的实际代码比 ThrowIfCancellationRequested
更复杂 - 即,如果它实际上是 监听 CancellationToken
- 那么对 Cancel
的调用可以在 调用 Cancel
中从 执行 StartSyncTest
的剩余部分,这将导致对 HttpContext.Current
.
总结一下:
- 我建议完全放弃这种方法;它在网络农场上根本不起作用。相反,将 "task state" 保存在外部存储系统中,例如数据库。
- 不要跨线程传递
HttpContext
。
一位同事提供了对 Task.Factory.StartNew
的替代调用(在 StartAsyncTest
内):
await Task.Factory.StartNew(() =>
{
StartSyncTest(sSearchId, rCancellationToken);
},
rCancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext());
这个实现似乎解决了异步问题。现在以后对 CancelAsyncTest
的调用会成功并按预期取消任务。