I'm calling Class Library method within WPF app, using CancellationToken
. Method exports data to Excel from Oracle, and sometimes operation takes too long or doesn't complete, so I tried with implementing cancellation.
WPF app - button click for export and export method:
public async void Export_Execute(object parameter)
{
SaveFileDialog dialog = new SaveFileDialog
{
InitialDirectory = Environment.GetFolderPath(
Environment.SpecialFolder.Desktop).ToString(),
Filter = "Excel |*.xlsx",
Title = "Save as"
};
if (dialog.ShowDialog() == true)
{
if (await Task.Run(() => GetData(dialog.FileName, DateTime.Now)))
{
var m = MessageBox.Show("Export complete, wish to open Excel file?",
"Export", MessageBoxButton.YesNo);
if (m == MessageBoxResult.Yes)
{
Process.Start(dialog.FileName);
}
}
}
}
GetData
method:
public bool GetData(string _filename, DateTime? _date)
{
try
{
using (OracleConnection con = new OracleConnection(conn_string))
{
con.Open();
MyTokenSource = new CancellationTokenSource();
OracleCommand cmd = new OracleCommand("MySchema.Employees", con)
{
CommandType = CommandType.StoredProcedure
};
cmd.Parameters.Add("date_in", OracleDbType.Date).Value = _date;
cmd.Parameters.Add("result", OracleDbType.RefCursor,
ParameterDirection.Output);
var export = new MyExports();
export.Export_DataReader(cmd, MyTokenSource.Token);
return true;
}
}
catch (OperationCanceledException)
{
MessageBox.Show("Operation cancelled by user.", "Export data")
return false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
}
If something goes wrong in this WPF method, I want an actual error displayed. But If user cancels GetData
method manually, I want to display a predefined message.
C# Class library code:
private static System.Timers.Timer CancelExport { get; set; }
public void Export_DataReader(OracleCommand _cmd, CancellationToken? _cancel)
{
//Doing long operations here for exporting to Excel...
// Timer gets enabled here too - in more or less beginning of the code...
Monitor(_cmd, _cancel);
}
private void Monitor(OracleCommand _cmd, CancellationToken? _cancel)
{
if (_cancel != null)
{
CancelExport = new System.Timers.Timer();
CancelExport.Elapsed += (sender, e) => MonitorEvent(sender, e, _cmd, _cancel);
CancelExport.Interval = 2000;
CancelExport.Enabled = true;
}
}
private static void MonitorEvent(object s, ElapsedEventArgs e, OracleCommand _cmd,
CancellationToken? _cancel)
{
if (_cancel.Value.IsCancellationRequested)
{
CancelExport.Enabled = false;
// When uncommented, this successfully returns Oracle exception
// Leaving like this, code just goes through,
// It ignores ThrowIfCancellationRequested()
// _cmd.Cancel();
//This doesn't return OperationCancelledException to WPF App so
//code keeps running like no error happened, even though error
//is visible in Output window
_cancel.Value.ThrowIfCancellationRequested();
}
}
So, when I hit MyTokenSource.Cancel()
in WPF app, I get Oracle.DataAccess.dll error exception If I want, but not OperationCancelledException
from CancellationToken
.
What should I do to get OperationCanceledException
back to my WPF app method?
I would try using creating separate cancellation token sources, one that cancels automatically after a delay, and one for the user to press and use CreateLinkedTokenSource to combine them:
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(42));
var manualCts = new CancellationTokenSource();
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, manualCts.Token);
try
{
await DoAsyncOperation(linkedCts.Token);
}
// Or catch(Exception) if some other exception type if thrown
catch (OperationCanceledException)
{
if (manualCts.Token.IsCancellationRequested)
{
// Handle manual cancel
}
else if(timeoutCts.Token.IsCancellationRequested)
{
// handle timeout
}
}
This should give you a way to determine what caused the cancellation.
What should I do to get OperationCanceledException back to my WPF app method?
This is up to the library. Well designed libraries should throw OperationCanceledException
when cancelled, but not all libraries are well designed. The fallback is to catch whatever exception type is thrown, or just all exceptions, and check if the cancellationTokens are triggered.