0

I have searched StackOverflow and not found a definitive answer to my particular situation. I have a method:

public async Task InitializeMachine()
{
    await DoSomeWork1();
    // other stuff
    await DoSomeWork2();
    // other stuff
}

Notice the signature it returns is Task, not Task<T>. It would be difficult (if not impossible to rewrite all the methods like this. I would like to call this method from a synchronous method AND have it wait for the InitializeMachine method to completely finish. Again, it would be hard to change it into an async method (I'll describe why below for those interested). I have looked at some related questions and references such as:

https://msdn.microsoft.com/en-us/magazine/mt238404.aspx and

await works but calling task.Result hangs/deadlocks and

How to call asynchronous method from synchronous method in C#?

The answers seem to be old (perhaps there is a newer better way?) and at least in many cases, they seem to depend on the async method returning Task<T>, not just Task. I have

void Initialize()
{
    //InitializeMachine(); // Perfectly legal, but will return at first await
     //return Task.Run(() => InitializeMachine()).GetAwaiter().GetResult();  // can't get this to compile. My typo? Or because it's Task not Task<T> ? This is the "Thread Pool Hack" in first reference above by Stephen Cleary
     var task = Task.Run(() => InitializeMachine()); // these 2 lines work!
        task.Wait();  // but see heated argument in 2nd reference in the answer by Herman Schoenfeld. Is it safe?
        Task task = Task.Run(async () => await InitializeMachine()); // also works. Comes from 3rd reference by author "Tohid"

}

Can someone tell me which method I should be using and why? As I said, I tried to do the research but found myself a little lost on all the arguments as to why there could be a potential deadlock and other problems. Adding to my confusion was all the disagreements. Surely by 2018 there is a definitive way?

Thanks, Dave

P.S. For those who care, I'm playing with the SMC state machine library. See: https://sourceforge.net/projects/smc/ Briefly, state machine code is automatically generated from a text file. BUT the code generated is synchronous methods. Yes, I could hack the generated code (but it will get overwritten) or rethink the entire problem (perhaps InitializeMachine should be not by asynch), but my question would remain nonetheless. It seems to me there are times where synchronous methods need to call async ones and they should wait until the async method is complete!

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
Dave
  • 8,095
  • 14
  • 56
  • 99

2 Answers2

2

You should be using this:

InitializeMachine().GetAwaiter().GetResult();

This will ensure that any exception raised at InitializeMachine is not wrapped into an AggregateException. Note that the return statement is not needed since you aren't returning anything (void method).

Edit
Since you are using WPF, it would be better if you just kept the async-all-the-way-down pattern:

public async void InitializeMachineAsync()
{
    await DoSomeWork1().ConfigureAwait(false);
    await DoSomeWork2().ConfigureAwait(false);
}

InitializeMachineAsync can be any event (like Window_Load)

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • 1
    Actually, with out the `Task.Run`, if the code is running in a UI context this will cause the UI to deadlock, this is due to `await DoSomeWork1();` not having a `.ConfigureAwait(false)`, also we don't know what is inside the function, it could deadlock there too. By adding a `Task.Run` you get the code off the UI context and it is safe to block it. – Scott Chamberlain Jan 26 '18 at 18:37
  • @ScottChamberlain You are right there, been some time since I worked with desktop apps with UI threads, will edit – Camilo Terevinto Jan 26 '18 at 18:39
  • Thank you Scott and Camilo for your prompt replies. I note (without taking a side - I'm out of my depth!) that people have cautioned against GetAwaiter.GetResult see https://stackoverflow.com/questions/22628087/calling-async-method-synchronously/22629216 answer by Diego Torres and the comments about deadlocks and compiler use. – Dave Jan 26 '18 at 18:56
  • @Dave That depends entirely on where and how it's used. That's why I commented on your question to include the specific framework being used. It also depends a lot on what your methods actually do internally – Camilo Terevinto Jan 26 '18 at 18:58
  • I don't believe GetAwaiter().GetResult() will work in my case where I'm returning Task, NOT Task<>. See my original question. I did try it. Perhaps just var task = Task.Run(() => InitializeMachine()); wait task; ??? – Dave Jan 26 '18 at 19:00
  • @Dave Whether you return `Task` or `Task` is meaningless, it gets the result from the `Task`. A non-generic `Task` just won't return any useful value, but you can still use it. Again, please tag the actual framework or specify that it's a generic library/code for reuse – Camilo Terevinto Jan 26 '18 at 19:01
  • Camilo, Framework is WPF. In general, I'm NOT dependent on calling GUI elements, although it would be nice to know how to do that too. (Like if code made button red!). There are certainly NOT .ConfigureAwait in most (all?) the methods I'm concerned with. – Dave Jan 26 '18 at 19:03
  • @Dave See my edit. But: there's no one piece of code that fits all needs, be aware of that – Camilo Terevinto Jan 26 '18 at 19:08
  • Camilo. First my apologies. Your suggestion from your first unedited post worked. That is: Task.Run(() => InitializeMachine()).GetAwaiter().GetResult(); It was my insistence on putting "return" in front of it that caused the compile time error. Second, you had me sold on this solution! Assuming, I'm not dependent on GUI elements, isn't it fine? Or does it wrap the exception as an AggregateException? [Which can be unwrapped, no?]. Having to go put "ConfigureAwait(false)" everywhere seems like a lot of work. And why isn't that the default if it's the way to go? – Dave Jan 26 '18 at 19:19
  • Because the compiler doesn't do the same when `ConfigureAwait(false)` is used. You are definitively not the first to ask that question, there are good answers to it. Any UI work won't work with both solutions posted (you'd be attempting to change a UI object from another thread). The last edit is what I'd recommend though, since you'd not be using synchronous blocks for asynchronous work. – Camilo Terevinto Jan 26 '18 at 19:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/163983/discussion-between-dave-and-camilo-terevinto). – Dave Jan 26 '18 at 21:52
  • Thanks for the answer and extended discussion! Marked as answer. – Dave Jan 27 '18 at 14:38
-1

the simplest thing to do is

InitializeMachine().Wait();

there are some considerations around exception handling

Z .
  • 12,657
  • 1
  • 31
  • 56
  • Don't do this - it can deadlock in ASP.Net and other cases. – Mike S Jan 26 '18 at 18:47
  • @MikeS It would be good if you could mention from where you got the idea that the OP mentioned ASP.NET. – Camilo Terevinto Jan 26 '18 at 18:51
  • I said "ASP.Net and other cases" - that includes UIs and potentially anything that's using a synchronization context. That means any library that may eventually be in the transitive closure of something called in those cases. It gets very broad. To be safe, avoid a construct that is almost guaranteed to lead to random deadlocks in broad cases. The fact they quoted articles identifying this problem (ie, https://stackoverflow.com/questions/17248680/await-works-but-calling-task-result-hangs-deadlocks/32429753#32429753 ), suggests that they need a broad solution. – Mike S Jan 26 '18 at 18:58