Reputation: 349
I wrote a small computer game where you can mine gold. The game is used to understand asynchronous programming with async and await. The loop menu offers three options: Mine gold, view evaluation, and exit.
The GoldMining method makes heavy use of the CPU. The probability to get one gram of gold during a mining is 1 in 10.000.000. Therefore it takes a long time until 1000g of gold are mined.
You start mining gold asynchronously from the loop menu. Then you can use option 2 (show evaluation) at any time to see the amount of gold you have mined so far.
Although the GoldMining method is very time consuming, the loop menu does not freeze. That's nice. However, I call the asynchronous method GoldMining without await. Now my question: Is this way of calling asynchronous methods to prevent the program from freezing a recommended approach?
//Evaluation contains the gold stock that was mined in total.
//The class definition is done at the very bottom.
Evaluation evaluation = new Evaluation();
await Main(evaluation);
static async Task Main(Evaluation evaluation){
string input="start";
while(input!="exit"){
Console.WriteLine("choose an option:");
Console.WriteLine("1 gold mining");
Console.WriteLine("2 show evaluation");
Console.WriteLine("3 exit.");
input=Console.ReadLine();
switch(input){
//Call GoldMining without await
case "1":GoldMining(evaluation);break;
case "2":Console.WriteLine(evaluation.gold);;break;
case "exit":input="exit"; break;
default:input="exit";break;
}
}
}
static async Task GoldMining(Evaluation evaluation){
Random rand= new Random();
int yield=0;
Console.WriteLine("Gold mining begins");
await Task.Run(() =>
{
while(yield<1000){
int chance =rand.Next(0,10000000);
if(chance==1){yield++;evaluation.gold++;}
} 2
});
Console.WriteLine("1000g Gold minded");
}
public class Evaluation{
public int gold{get;set;}
public Evaluation(){
this.gold=0;
}
}
Upvotes: 0
Views: 598
Reputation: 456322
The game is used to understand asynchronous programming with async and await... The GoldMining method makes heavy use of the CPU.
Asynchronous code is not a natural fit with CPU-bound methods. For CPU-bound methods, you want parallel code or multithreading.
If you want to learn asynchronous code in a more natural way, use an I/O-bound operation. E.g., an HTTP request to https://deelay.me/10000/https://google.com/
.
Note that Task.Run
is a form of multithreading (pushing work onto the thread pool). So your code is already using both multithreading and asynchrony - which is fine, but complicates things while learning.
However, I call the asynchronous method GoldMining without await. Now my question: Is this way of calling asynchronous methods to prevent the program from freezing a recommended approach?
No, certainly not. This is a kind of "fire and forget", which is dangerous in general. You almost always want to call await
; if you do need to spin off a separate "top-level task", then you normally want to capture the task into a local variable and await
it later. Otherwise, your app may exit before the operation is complete, and also you may not be aware of any exceptions that have happened.
I'm a bit irritated because I learned from a couple of Youtube videos that the big advantage of asynchronous programming is that the program doesn't freeze while it's handling a complex task.
You're currently writing a Console application. While Console apps are nice for learning most technologies, I believe using Console for learning async
is not a good idea. Console apps can be asynchronous, but it's a very unusual environment for asynchronous applications. A much more natural environment is a client app (UI) or a server app (ASP.NET), both of which have framework-level support for asynchrony.
So, when the videos mention not "freezing", they're referring to how await
(on a UI app) can keep the UI thread free to handle other messages. Since a Console app doesn't have a UI message loop, the same benefit doesn't apply there. You can build it, but it's way better IMO to learn proper async
/await
with either a UI app or an ASP.NET app first.
Upvotes: 3
Reputation: 56467
So, first of all your code is not thread safe. You need to use Interlocked.Increment(ref evaluation.gold)
instead of evaluation.gold++
and volatile read (or even dummy Interlocked.CompareExchange
, see this) at case "2". This may or may not matter, depending on whether your task scheduler is multithreaded or not (it is by default). Well, actually if your scheduler is single threaded then your code wouldn't work at all, due to an infinite loop (adding await Task.Yield();
inside the while loop in Main()
would fix that). Either way, the rule of thumb is: always write a thread safe code when doing async.
Now my question: Is this way of calling asynchronous methods to prevent the program from freezing a recommended approach?
Yes, it is a good idea to spawn a background worker, in fact there's no other way to achieve that (I mean, you can spawn a thread instead of async task, but the idea is the same). And no, it is not a good idea to leave it without supervision. What if it fails? You have no option to react to it, even to log an error. What if GoldMining()
is called multiple times? Maybe it is allowed, but without limit?
You could keep the result of calling GoldMining()
around (without await
) and check its status from time to time. You could keep it in a list and disallow too many. But perhaps a better approach would be to have a separate queue, a separate thread/async task that operates on it and also gracefuly handles all the errors. All of that wrapped into a single class.
Upvotes: 1