-2

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?

17
  • 2
    Are you looking for MyTokenSource = new CancellationTokenSource(timeout);? E.g. MyTokenSource = new CancellationTokenSource(1000); to wait 1 second before cancellation Commented Sep 25, 2023 at 13:57
  • @DmitryBychenko, no I just simply want to throw back OperationCancelledException when user cancels TokenSource. Commented Sep 25, 2023 at 14:21
  • Could you include in the question the calling site of the GetData method? I assume it's an event handler of a UI control. Commented Sep 25, 2023 at 14:49
  • 1
    Where is GetData called? And where does export.Export_DataReader throw? Your code sample is a mess. Please read this and update your question accordingly. Commented Sep 25, 2023 at 15:57
  • 1
    @TheodorZoulias, see my edited question for GetData method. You assumed correctly, It's a button click event. Commented Sep 26, 2023 at 7:27

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I will try, but don't see how this would help me. I thinik the problem is that token exception simply gets swallowed by timer. That's why I posted Oracle error too - which doesn't get swallowed.
@Lucy82 Sorry, missed the last bit of the question. I have updated the answer.
I've tried using your answer, and It actually does work:)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.