-2

Scenario: I want to call an async task from a sync method but it's an independent task from which I don't need any return and I plan control all exceptions in the task, so no exception will be launched.

What's the best solution to call it without deadlocking and trying to spend less resources?

Example:

//Sync Method
public MySyncMethod()
{
    //Maybe this?
    Task.Run(() => KeepAlive(keepAliveSec));
    //or this?
    KeepAlive(keepAliveSec).GetAwaiter();
    //Any other better option?
}

/// <summary>
/// Starts Heartbeat, Async Task
/// </summary>
/// <param name="sec"></param>
/// <returns></returns>
public async Task KeepAlive(int sec)
{
    try
    {
        while (!CancelationToken.IsCancellationRequested)
        {
            if ((DateTime.Now - LastMessageDate).Seconds<sec)
            {
                SendMessage(new MessageV4() { MsgType = MsgTypeEnum.HeartBeat });
            }
            await Task.Delay(60000);
        }
    }
    catch (Exception ex)
    {
        //Just do what you want to control any Exception
        Log.Debug(ex);
    }
}

PS -I've searched here before, but still I've not found a clear answer

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Rui Caramalho
  • 455
  • 8
  • 16
  • Where's the CancellationToken coming from? – Fildor Jul 14 '20 at 14:56
  • It's a class that has a property `public CancellationToken CancelationToken { get; set; }` – Rui Caramalho Jul 14 '20 at 14:58
  • 1
    No because the constructor builds a default if none is sent. – Rui Caramalho Jul 14 '20 at 15:00
  • 1
    Why does `MySyncMethod` need to be synchronous? – Johnathan Barclay Jul 14 '20 at 15:09
  • > Why does MySyncMethod need to be synchronous? It's a valid point, but for this exercise let's assume this is the problem. Imagine you want to call inside the constructor the task `KeepAlive` – Rui Caramalho Jul 14 '20 at 15:16
  • Have you considered `ThreadPool.QueueUserWorkItem`? – Fildor Jul 14 '20 at 15:25
  • I am still struggling with this line `if ((DateTime.Now - LastMessageDate).Seconds – Fildor Jul 14 '20 at 15:29
  • 1
    @RuiCaramalho Do you want to call this inside the constructor? If so, there are much cleaner solutions than blocking or fire-and-forget. – Johnathan Barclay Jul 14 '20 at 15:41
  • 3
    You want to send some message every `X` seconds to keep... something... alive, and you don't want to block the main thread? Any reason not to use [`System.Threading.Timer`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer)? – Joshua Robinson Jul 14 '20 at 15:45
  • It _appears_ that you want to call the async method but not wait for the outcome. If so, this pattern is called "fire and forget". See marked duplicate (and use that phrase in your own search of the site) for extensive discussion of pros/cons/implementation details. If that's not what you want (i.e. you want to wait in the non-async method for the async method to complete), then that is indeed a deadlock risk, but how to address that depends on your specific scenario and you haven't offered a [mcve] that shows what deadlock risk you care about, making a question with that goal unanswerable. – Peter Duniho Jul 14 '20 at 16:15

1 Answers1

-1

By far the best solution would be to make MySyncMethod async.

If for whatever reason his is not possible, the next best solution may be to just make KeepAlive sync by replacing Task.Delay with Thread.Sleep.

Either way, if MySyncMethod is going to run synchronously you are going to have to block the thread at some point, unless you are doing fire-and-forget, which is rarely a good idea; even if you are 100% confident that KeepAlive will never throw an exception, there's always the chance that the process will terminate before the Task has completed.

However, you could do this by simply discarding the Task:

public MySyncMethod()
{
    _ = KeepAlive(keepAliveSec);
}

If none of these options suit, then you're into the territory of workarounds.

If your app does not have a synchronisation context, you can simply block using Wait(), without risking a deadlock:

public MySyncMethod()
{
    KeepAlive(keepAliveSec).Wait();
}

If you do have a synchronisation context then you may be able to use Task.Run() to offload to the threadpool:

public MySyncMethod()
{
    Task.Run(() => KeepAlive(keepAliveSec)).Wait();
}

Although with this approach KeepAlive would not be able to update the UI, or access HttpContext.

It also introduces overhead, and uses 2 threads simultaneously, so should only be considered as a last resort.


If MySyncMethod is actually a constructor, you could do something like this:

class MyClass
{
    Task task;
    
    MyClass(int keepAliveSec)
    {
        task = KeepAlive(keepAliveSec);
    }

    Task WaitAsync() => task;
}

Then you can use like this:

var myClass = new MyClass(10000);

// Do some other stuff..

// Make sure myClass has completed before exiting
await myClass.WaitAsync();

You could, of course, just make the task field public, but having a WaitAsync method is more explicit.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
  • I'm using the `KeepAlive()` inside the constructor of the class (I don't think I can make a contructor async?). Another doubt I've is? when using 'KeepAlive(keepAliveSec).Wait();' we are saying to wait for the result. Will this not cause any problem? compared with the `.ConfigureAwait(false);`? – Rui Caramalho Jul 14 '20 at 15:58
  • Just to clarify, the idea of the KeepAlive() task is fire and forget I've no intention of getting any result... – Rui Caramalho Jul 14 '20 at 16:00
  • @RuiCaramalho You can't make the constructor `async`, but you can store the `Task` and wait later. I'll add a simple example. – Johnathan Barclay Jul 14 '20 at 16:00