0

I am creating a WPF GUI application in vb.net that needs to interact with a COM API to perform some processor intensive work. Initially, I placed the work in a BackgroudWorker:

worker = New BackgroundWorker()
AddHandler worker.DoWork, AddressOf tsm.SAP_DoWork
AddHandler worker.ProgressChanged, AddressOf tsm.SAP_ProgessChanged
AddHandler worker.RunWorkerCompleted, AddressOf tsm.SAP_Completed

worker.RunWorkerAsync()

The tsm object has a reference to the mainWindow which is updated in the ProgressChanged handler. This works perfectly, except for one thing: I need to be able to abort the SAP_DoWOrk function instantly if the user clicks an abort button.

Now from my understanding of the cooperative cancellation model of the BackgroundWorker, this would mean enabling cancellation:

worker.WorkerSupportsCancellation = True

And then checking for this flag in the SAP_DoWork function. The problem is that this function calls many seperate processor intensive functions in several subclasses and also has a significant amount of logic, and I wish for the thread to stop immediately when cancelled.. So now it appears that I have two options:

  1. Place a check for cancellation before every action and abort the thread all the way up the call stack. This would mean the subclasses would also need to know about the cancellation and seems like an awful way of doing this.
  2. Abondon the BackgroundWorker way of doing things and find some functionality that supports killing threads safely. The data and progress of the background work does not need to be saved, as long as it doesn't affect the main thread.

I really don't want to do 1, so is there a way of doing 2 in VB.NET WPF applications? Is there some fundamental misunderstanding I have of how the BackgroundWorker works?

ekke
  • 1,280
  • 7
  • 13
  • Option 2 wouldn't be "cooperative"... – Clemens Nov 05 '19 at 09:17
  • See [C# Thread Termination and Thread.Abort()](https://stackoverflow.com/questions/2251964/c-sharp-thread-termination-and-thread-abort) – Rekshino Nov 05 '19 at 09:20
  • [worker.CancelAsync()](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker.cancelasync) (from outside the `DoWork` method, in a Button's Click event, for example). In the `DoWork` method, check whether [CancellationPending](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker.cancellationpending) is `true`. In that case, set `e.Cancel = true` – Jimi Nov 05 '19 at 09:20
  • @Rekshino it seems that even with Thread.Abort() there are no guarantees and generally it's considered bad practice. Would the answer then be to use processes instead? – ekke Nov 05 '19 at 09:30
  • @Jimi I don't want to do that (as I mentioned in the question) because I would need to put a check before each action, including in functions lower down the call stack. this would be a huge mess of "if x then abort" calls throughout the logic, which seems not ideal. – ekke Nov 05 '19 at 09:33
  • Then run Tasks with a CancellationToken that you pass around. – Jimi Nov 05 '19 at 09:36
  • @jimi would you be able to provide an example? It's not clear to me how that would work. – ekke Nov 05 '19 at 09:41
  • __It's up to you which way you wil go and it depends on the code and requirements.__ I personally probably would use a `Thread.Abort()`. For some COM operation (also in the dedicated thread) I would use a Task with a timeout, because e.g. `ComPort.Close` make often problems. – Rekshino Nov 05 '19 at 10:11
  • You just need a [CancellationTokenSource](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource) (declared as an instance field) and a [CancellationToken](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken). Then, before running a the main Task, set `ctsSource = new CancellationTokenSource(); CancellationToken ctsToken = ctsSource.Token;`. `...` – Jimi Nov 05 '19 at 10:24
  • You can then run a Task that awaits one or more async sub-Tasks: `await Task.Run(async ()=> { if (ctsToken.IsCancellationRequested) return; await Task.Delay(2000); if (ctsToken.IsCancellationRequested) return; await Task.Delay(2000); }, ctsToken);`. Anywhere/anytime (you can also pre-set a time-out), you can set `ctsSource.Cancel(false);` – Jimi Nov 05 '19 at 10:24
  • `await Task.Delay(2000);` simulates an async method's work. If you evaluate the examples in the Docs, don't use `TaskFactory.StartNew()`. It's dangerous in your case. – Jimi Nov 05 '19 at 10:26
  • @Jimi thank you for those examples. The problem here is that this is effectively the same problem I have anyway. My task has over 30 different calls to the COM object, meaning that before each one of these I would need to put the test `if (ctsToken.IsCancellationRequested) return;`, essentially copy/pasted before each one. This will make the code ugly and difficult to maintain which is why I want to avoid it. Also, this would need to be added to the other classes/functions that also make several calls to this COM object, and hence they would need to be aware of this threading functionality too – ekke Nov 05 '19 at 11:08
  • Aborting a Thread with COM interop objects initialized around is probably the worse thing you can do. You can check for a cancellation request before a method call. It depends on how much time these methods take to complete. I don't know how your code is structured or what is doing. Maybe, you can try to reduce the complexity of your code to meet this requirement. – Jimi Nov 05 '19 at 11:15

0 Answers0