I'm having a bit of trouble getting an async operation working (new to async). My goal is to have a button "Load Data" go out and retrieve some data from the database and populate a grid. The database can be somewhat far away for some users and this operation may take some time. Considering this, I want the users to be able to have the option to cancel and choose to retrieve a smaller set of data.
I have it mostly working with the current flow:
- User clicks on "Load Data..." button
- Button changes to "Cancel" and async operation to retrieve the data kicks off
- Data is retrieved and the grid is populated
This is all working well EXCEPT, if the user clicks cancel, it still takes the same amount of time that it would have taken to get all the data for the grid to come up empty. This leads me to believe that the long running operation actually didn't get cancelled... however, when I debug in the "FindForLocationAsync" method, the cancellation token does stop the iterative operation and return from the method early if the user requests a cancellation.
I've been reading as much as I can on this for quite a while but, I'm at a bit of an impasse now. Any help would be very much appreciated.
Cancellation Token Source
CancellationTokenSource cancellationTokenSource = null;
Button Click Method
private async void btnSearch_Click(object sender, EventArgs e)
{
gridLog.DataSource = null;
Cursor = Cursors.WaitCursor;
if (btnSearch.Text.ToLower().Contains("load"))
{
btnSearch.Text = "Cancel";
btnSearch.ForeColor = Color.White;
btnSearch.BackColor = Color.Red;
//get params to pass
/* snip */
cancellationTokenSource = new CancellationTokenSource();
await Task.Run(() =>
{
var ds = DocLog.FindForLocationAsync(docType, subType, days, currLocation.ID, cancellationTokenSource.Token).Result;
gridLog.DataSource = ds;
});
btnSearch.Text = "Load Data...";
btnSearch.ForeColor = Color.Black;
btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
else
{
cancelSearch();
btnSearch.Text = "Load Data...";
btnSearch.ForeColor = Color.Black;
btnSearch.BackColor = Color.FromArgb(225, 225, 225);
}
Cursor = Cursors.Default;
}
Cancel Method
private void cancelSearch()
{
if (cancellationTokenSource != null) cancellationTokenSource.Cancel();
}
Long Running Method
public async static Task<BindingList<DocLog>> FindForLocationAsync(string DocType, string SubType, int? LastXDays, Guid LocationID, CancellationToken CancellationToken)
{
BindingList<DocLog> dll = new BindingList<DocLog>();
using (SqlConnection sqlConnection = new SqlConnection(Helper.GetConnectionString()))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand((LastXDays == null) ? "DocLogGetAllForLocation" : "DocLogGetAllForLocationLastXDays", sqlConnection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Parameters.Add("@DocType", SqlDbType.NVarChar, 30).Value = DocType.Trim();
sqlCommand.Parameters.Add("@SubType", SqlDbType.NVarChar, 30).Value = SubType.Trim();
sqlCommand.Parameters.Add("@LocationID", SqlDbType.UniqueIdentifier).Value = LocationID;
if (LastXDays != null) { sqlCommand.Parameters.Add("@NumberOfDays", SqlDbType.Int).Value = LastXDays; }
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
await Task.Run(() =>
{
while (sqlDataReader.Read())
{
if (CancellationToken.IsCancellationRequested)
{
dll = new BindingList<DocLog>();
break;
}
else
{
DocLog dl = readData(sqlDataReader);
dll.Add(dl);
}
}
});
}
}
return dll;
}