0

Yes, I know, there are tons of threads on this topic. I read a lot of them and used them often (more or less) successfully. Now I got an old DLL (programmed in .net 4.0) and that is using BackgroundWorkers to fire result events. Whatever I try to stop and wait for such a result seems to miss its mark. But maybe some of you have ideas that I haven't tried yet.

I register the answer event in thread 1 (according to Thread.CurrentThread.ManagedThreadId), call the method to connect and wait with a SemaphoreSlim for the answer. But in each and every constellation I get the answering event AFTER the timeout occurred. The event is on thread 3 and when I tried to raise the AwaitAsync() (also with Await) of the SemaphoreSlim on a special Thread.Run(() => ...); it was on thread id 8. But still thread number 3 always just comes AFTER the timeout.

private void ConnectDevice()
{
    MobileDevice.DeviceConnected += new DeviceConnectedHandler(MobileDevice_Connected);
    ...
    mSignal = new SemaphoreSlim(0, 1);
    MobileDevice.Connect();
    // Task.Run(() => MobileDevice.Connect());
    int i = Thread.CurrentThread.ManagedThreadId;
    var task = Task.Run(() => mSignal.WaitAsync(new TimeSpan(0, 0, cTimeout)).GetAwaiter().GetResult());
    // Task<bool> task = Task.Run(async () => await WaitForSemaphore());
    // var result = task.Wait();
    IsConnected = task.Result;
    // WaitForSemaphore();
    ...
}

private async Task<bool> WaitForSemaphore()
{
    int j = Thread.CurrentThread.ManagedThreadId;
    if (!await mSignal.WaitAsync(new TimeSpan(0, 0, 5)))
    {
        throw new MobileDeviceException("Device timed out");
    }
    return true;
}
//private void WaitForSemaphore()
//{
//    if (!mSignal.Wait(new TimeSpan(0, 0, cTimeout)))
//    {
//        throw new MobileDeviceException("Device timed out");
//    }
//}

private void MobileDevice_Connected(object sender, DeviceConnectedEventArgs e)
{
    int k = Thread.CurrentThread.ManagedThreadId;
    mSignal?.Release();
    ...
}

And yes, I know this is chaotic. But I wanted to show you, that I tried a lot already. I tried a lot more, but deleted also a mass of mistakes. I begin to think, that, even if the answer comes on thread id 3, the listener to these events is still the main thread (id 1). And as long as that is blocked, the event doesn't gets fired.

Am I right? And how do I get around this? Register the event on a different way?
Oh, I nearly forgot: I am serving an Interface here and this will become a plugin for a complex application, so I cannot make the Connect-Method async and use the async/await-Pattern. I have to call the Connect of the device, block the main thread till the answer arrives and then release it, so the main part of the application can continue.

Anyone an idea of solving this?


Edit (the 1st): Ok, to sort some confusions out. This is a plugin that is called from a non-async method. I cannot change the calling method to an async one or else I would have to reprogram a few hundred thousand lines of code.

The call comes from the main program and looks like this: firstDevice.Connect(); I COULD change that to something within reason, but I cannot use something like: await firstDevice.Connect(); or else I would have to change the main programs calls all to async. And this is simply out of question.

The connect method inside the plug-in I could change. At this moment it does nothing more than to call the ConnectDevice(), so I could test some things with async, SemaphoreSlims, and so on.

And as soon as I use an await inside an async method, the calling thread moves on. There would also have to be an await, but you cannot use await outside of async methods.

What seems strange to me is, that Thread.CurrentThread.ManagedThreadId says that both threads are thread 1. But when I step through they are clearly moving asynchronously.


Edit (the 2nd): I heard a clue. Maybe the problem here is the BackgroundWorker of the API. A colleague of mine once heard that the BackgroundWorker blocks the GUI-thread, when it is started on the GUI-thread. So the events of the API cannot get to me on thread 3 until the GUI-thread is released. So the solution would be to call the MobileDevice.Connect(); on a different thread. But it seems that the API will have to change. So we will discuss this internally. As soon as I have a solution I will update this a last time for anyone interested.


Edit (the 3rd): Ok, it seems nearly all of this solutions are working my problem was really with this goddamn BackgroundWorker. The API communicated with the mobile device on thread 1. And as soon as you block thread 1, there is also a block in the communication between API and device, so the answer of the API never comes... But thanks anyway for you help. ;)

Marcel Grüger
  • 885
  • 1
  • 9
  • 25
  • 1
    I'm confused. Why can't you use async/await? Don't see what interfaces have to do with it. But the design seems overly complex. – Zer0 May 06 '22 at 12:18
  • The interface says: void Connect(); So I have to use void Connect and cannot use async void Connect()... maybe I should have added, that I build a plugin here... I will add it as info. – Marcel Grüger May 06 '22 at 12:20
  • 1
    I don't see the interface or any public methods. Hard for me to understand what you're trying to do here. Even if the interface defines the method as `void Connect` you can implement it as `async void Connect()`. Any chance you can boil this down to an MRE? Or some more specific issue. – Zer0 May 06 '22 at 12:24
  • 1
    URgs... my son broke a finger, have to go. Maybe I can answer that before monday. Sorry. – Marcel Grüger May 06 '22 at 12:29
  • So you want to call `MobileDevice.Connect()`, and block the calling thread at this point until the `Connected` event handler is invoked, correct? – Theodor Zoulias May 06 '22 at 14:20
  • @TheodorZoulias That is correct. – Marcel Grüger May 09 '22 at 05:55

1 Answers1

2

You may wrap the Connect call in a TaskCompletionSource:

public static class DeviceExtension
{
    public static Task ConnectAsync(this Device device)
    {
        var tcs = new TaskCompletionSource<object>();

        device.DeviceConnected += (s, e) => tcs.SetResult(null);
        device.Connect();

        return tcs.Task;
    }
}

which you would call like

await MobileDevice.ConnectAsync();

or in a synchronous context like

MobileDevice.ConnectAsync().Wait();
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Thanks for the answer, but I cannot call the method with an `await`. That is the whole point in it. I edited my question to explain why. See above. – Marcel Grüger May 09 '22 at 05:55
  • @MarcelGrüger how about blocking synchronously on the `ConnectAsync`? `MobileDevice.ConnectAsync().Wait();` – Theodor Zoulias May 09 '22 at 06:41
  • @TheodorZoulias That runs into a deadlock. I think that could be, because the semaphoreslim wait also blocks and both are blocking each other. But maybe I found the answer. I will update my thread. – Marcel Grüger May 09 '22 at 07:39
  • 1
    @MarcelGrüger you could try instantiating the `TaskCompletionSource` with the `TaskCreationOptions.RunContinuationsAsynchronously` as argument. Also check out [this answer](https://stackoverflow.com/questions/56844128/creating-an-awaitable-system-timers-timer/56844580#56844580) to a similar question, that shows how to detach the event handler after receiving the expected signal. – Theodor Zoulias May 09 '22 at 07:45