0

I would like the users of a web application to be able to cancel long running SQL queries on the server side (by using xhr.abort() method)

I'm using the Response.ClientDisconnectedToken to catch on the server side the event of the canceling of a request by the user (from here: https://stackoverflow.com/a/17713153/8790102)

The SQL query routine is done in an async method (from here: https://stackoverflow.com/a/24834029/8790102)

private async Task<DataTable> executeSelectQueryAsync(string SQL, Dictionary<string, object> BindObject = null)
{
    // This cancellationToken is tripped when the client abort the request
    CancellationToken canceltk = HttpContext.Current.Response.ClientDisconnectedToken; // Require IIS 7.5

    DataTable dt = new DataTable();

    // Only line to be logged in the server
    File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/data/logCanceledRequest.txt"), "CANCELING ENABLED" + Environment.NewLine);

    try
    {
        dt = await Task.Run(() =>
        {
            // If token is canceled, cancel SQL query
            canceltk.Register(() =>
            {
                File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/data/logCanceledRequest.txt"), "CANCELLING..." + Environment.NewLine);
                // scmd is of type OracleCommand
                if (scmd.Connection.State == ConnectionState.Open)
                    scmd.Cancel();
            });

            // Open the connection, execute the SQL command using OracleDataAdapter.Fill method and return a DataTable
            return executeSelectQuery_inner(SQL, BindObject);
        }, canceltk);
    }
    catch (TaskCanceledException ex)
    {
        try
        {
            File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/data/logCanceledRequest.txt"), "Cancelling query..." + Environment.NewLine);
            if(scmd.Connection.State == ConnectionState.Open)
                scmd.Cancel();
        }
        catch (Exception ex1)
        {
            File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/data/logCanceledRequest.txt"), "Cancel_ERROR:" + ex1.ToString() + Environment.NewLine);
        }
    }
    catch (Exception ex)
    {
        File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/data/logCanceledRequest.txt"), "OTHER EXCEPTION:" + ex.ToString() + Environment.NewLine);
    }

    return dt;
}

My problem is that the method registered in canceltk.Register() is not called when aborting the request (the corresponding text "CANCELLING..." is not logged).

In fact, no text is logged at all. I have no idea why.

If I use a Thread.Sleep(5000) before calling executeSelectQueryAsync and abort the request during these 5 seconds, then the TaskCanceledException is successfully raised and caught.

H4dr1en
  • 277
  • 2
  • 11
  • non-local database command objects always make me nervous. Can we see what `executeSelectQuery_inner` looks like please? – Damien_The_Unbeliever Aug 21 '18 at 13:53
  • As does manual logging rather than using a well written logging library (nervous, that is). You're incurring quite an overhead by not keeping the log file open and ready for writing. – Damien_The_Unbeliever Aug 21 '18 at 13:54
  • Agreed, especially since that manual logging was the cause of the problem - See my answer below! – H4dr1en Aug 21 '18 at 14:00
  • 1
    Yep, time to pick a proper logging library (that way you can use it when you need it and, if you're doing things right, can leave logging code in place even once things are working properly, ready for next time there's an issue to look into) – Damien_The_Unbeliever Aug 21 '18 at 14:05

2 Answers2

1

Task.Run schedules the execution of that code on a thread pool thread which is not controlled by AS.NET's synchronization context. That's why HttpContext.Current is null.

Besides that, in this context, Task.Run causes the execution of the request to be transferred to another thread pool thread, the request handling thread (another thread pool thread) is returned to the pool and when Task.Run finishes execution that thread is returned to the thread pool thread and another one is retrieved and populated with request handling data. You just used more resources for no benefit. Quite the contrary.

Code that escapes the ASP.NET context should not rely on it.

Doesn't the Oracle provider support asynchronous operations?

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • ODP.NET provides such async methods indeed, though they [are not async](https://www.exceptionnotfound.net/why-does-odp-net-have-async-methods-if-theyre-not-asynchronous/). What I really need here is _parallelism_, not _asynchrony_, to be able to cancel the SQL query. Would using the async operations of Oracle be better regarding performances? Would it be still possible to Cancel the query? How to proceed? Thanks for your help! – H4dr1en Aug 22 '18 at 06:09
  • 1
    If you need parallelism, `Task.Run` is a good option, but you should not depend on anything ASP.NET in there. Just retrieve the data. You have a lot of questions there for Oracle. I wouldn't know how to answer them. – Paulo Morgado Aug 22 '18 at 07:23
0

The method was in fact well called, but System.Web.HttpContext.Current then had a null value, causing a Exception not caught.

By replacing with System.Web.Hosting.HostingEnvironment.MapPath, the code is working successfully.

H4dr1en
  • 277
  • 2
  • 11