简体   繁体   中英

Async Web Service call not consistently asynchronous

I'm having issues creating an asynchronous web service using the Task Parallel Library with ASP.NET Web API 2. I make an asynchronous call to a method StartAsyncTest and create a cancellation token to abort the method. I store the token globally and then retrieve it and call it from a second method CancelAsyncTest . Here is the code:

// 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;
}

After I deploy this to IIS, the first time I call StartAsyncTest and then CancelAsyncTest (via the REST endpoints), both requests go through and it cancels as expected. However, the second time, the CancelAsyncTest request just hangs and the method is only called after StartAsyncTest completes (after 1100 seconds). I don't know why this occurs. StartAsyncTest seems to highjack all threads after it's called once. I appreciate any help anyone can provide!

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

This is probably not a great idea. You can store these tokens "globally", but that's only "global" to a single server. This approach would break as soon as a second server enters the picture.

That said, HttpContext.Current shouldn't be assigned to, ever. This is most likely the cause of the odd behavior you're seeing. Also, if your real code is more complex than ThrowIfCancellationRequested - ie, if it's actually listening to the CancellationToken - then the call to Cancel can execute the remainder of StartSyncTest from within the call to Cancel , which would cause considerable confusion over the value of HttpContext.Current .

To summarize:

  • I recommend doing away with this approach completely; it won't work at all on web farms. Instead, keep your "task state" in an external storage system like a database.
  • Don't pass HttpContext across threads.

A colleague offered a alternative call to Task.Factory.StartNew (within StartAsyncTest ):

await Task.Factory.StartNew(() =>
{
    StartSyncTest(sSearchId, rCancellationToken);
},
rCancellationToken, 
TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext());

This implementation seemed to solve the asynchronous issue. Now future calls to CancelAsyncTest succeed and cancel the task as intended.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM