40

I'm currently writing a C# console app that generates a number of URLs that point to different images on a web site and then downloads as byte streams using WebClient.DownloadDataAsync().

My issue is that once the first asynchronous call is made, the console app considers the program to be completed and terminates before the asynchronous call can return. By using a Console.Read() I can force the console to stay open but this doesn't seem like very good design. Furthermore if the user hits enter during the process (while the console is waiting for input) the program will terminate.

Is there a better way to prevent the console from closing while I am waiting for an asynchronous call to return?

Edit: the calls are asynchronous because I am providing a status indicator via the console to the user while the downloads take place.

p.campbell
  • 98,673
  • 67
  • 256
  • 322
Andrew Keller
  • 3,198
  • 5
  • 36
  • 51

4 Answers4

52

Yes. Use a ManualResetEvent, and have the async callback call event.Set(). If the Main routine blocks on event.WaitOne(), it won't exit until the async code completes.

The basic pseudo-code would look like:

static ManualResetEvent resetEvent = new ManualResetEvent(false);

static void Main()
{
     CallAsyncMethod();
     resetEvent.WaitOne(); // Blocks until "set"
}

void DownloadDataCallback()
{
     // Do your processing on completion...

     resetEvent.Set(); // Allow the program to exit
}
David Murdoch
  • 87,823
  • 39
  • 148
  • 191
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Thanks! This makes sense and works perfectly... much better than console.read! – Andrew Keller Oct 01 '10 at 15:56
  • I also find this useful in debugging Windows Services using a long-running `Task` as the primary worker. In `Program.Main()`, using a pre-processor directive like `#if DEBUG [debug hook] #else [normal service code] #endif` And then in the "debug hook" I just add a call to `resetEvent.WaitOne()` after `new MyService().OnStart()`. Then you can use VS debug to continuously run in debug mode without modifying your actual service code in any meaningful way. – Eric Lease Jan 27 '16 at 14:41
  • In the OP's specific case it might work, but in general this causes deadlocks if a background task schedules something on the main thread and waits for it to complete. Just a small warning. – relatively_random Jul 21 '17 at 13:45
29

Another option is to just call an async Main method and wait on the task returned.

static void Main(string[] args)
{
    MainAsync(args).Wait();
}

static async Task MainAsync(string[] args)
{
    // .. code goes here
}

Since .NET 4.5 you can now call GetAwaiter().GetResult()

static void Main(string[] args)
{
    MainAsync(args).GetAwaiter().GetResult();
}

What is the difference between .Wait() vs .GetAwaiter().GetResult()?

Community
  • 1
  • 1
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
6

If that is all your application is doing, I would suggest leaving out the async call; you specifically want the app to 'hang' until the download is complete, so using an async operation is contrary to what you want here, especially in a console app.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
  • I left this out in my problem explanation but during the download I display a progress indicator to the console window. We are talking thousands of images so it will be valuable to the end user to know what is going on. – Andrew Keller Oct 01 '10 at 15:51
  • That's fine; you can still update the console from the same thread, too; the user just won't be able to do anything but Control-C out, but that's the behavior you are looking for, anyway. – Andrew Barber Oct 01 '10 at 15:54
  • 2
    Using async can also be beneficial if you ever need to extend this to download multiple files, etc... wasn't sure of the rationale here, but if it's just one file, synchronous would be easier.. – Reed Copsey Oct 01 '10 at 15:57
  • @Reed I should have been clearer that I was assuming he's only doing a single file, yes. Thanks, Reed! +1 – Andrew Barber Oct 01 '10 at 15:59
1

It should be stated that in C# 7.1 Main can now be marked as async.

static async Task Main()
{
  await LongRunningOperation();
}
mariocatch
  • 8,305
  • 8
  • 50
  • 71