控制台应用在异步调用完成之前终止

40

我正在编写一个C#控制台应用程序,它生成多个指向网站上不同图像的URL,并使用WebClient.DownloadDataAsync()下载字节流。

我的问题是,一旦第一个异步调用被发出,控制台应用程序会认为程序已完成并在异步调用返回之前终止。通过使用Console.Read(),我可以强制控制台保持打开状态,但这似乎不是很好的设计。此外,如果用户在过程中按下回车键(当控制台正在等待输入时),程序将终止。

有没有更好的方法来防止控制台在等待异步调用返回时关闭?

编辑:调用是异步的,因为我正在通过控制台向用户提供状态指示器以进行下载。

4个回答

52

可以。使用ManualResetEvent,并让异步回调调用event.Set()。如果主要例程在event.WaitOne()上阻塞,它将等待异步代码完成后才退出。

基本的伪代码应如下:

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
}

谢谢!这很有道理,而且完美地运行了...比console.read好多了! - Andrew Keller
我也发现在使用长时间运行的Task作为主要工作程序来调试Windows服务时非常有用。在Program.Main()中,使用类似于#if DEBUG [debug hook] #else [normal service code] #endif的预处理器指令。然后在“debug hook”中,只需在new MyService().OnStart()之后添加一个对resetEvent.WaitOne()的调用即可。然后,您可以使用VS调试以连续运行调试模式,而不会以任何有意义的方式修改实际的服务代码。 - Eric Lease
在OP的特定情况下可能有效,但通常情况下,如果后台任务安排了一些主线程上的操作并等待其完成,则会导致死锁。只是一个小警告。 - relatively_random

29

另一种选择是直接调用异步 Main 方法并等待返回的任务。

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

static async Task MainAsync(string[] args)
{
    // .. code goes here
}
自 .NET 4.5 起,您现在可以调用 GetAwaiter().GetResult()
static void Main(string[] args)
{
    MainAsync(args).GetAwaiter().GetResult();
}

什么是 .Wait() 和 .GetAwaiter().GetResult() 的区别?


3
简洁明了,正是我所寻求的。 - Dylan Vester

6
如果你的应用程序只是做这个,我建议省略异步调用;你特别需要应用程序在下载完成之前“挂起”,因此在这里使用异步操作与你的意图相反,尤其是在控制台应用程序中。

我在问题说明中遗漏了这一点,但在下载过程中,我会向控制台窗口显示进度指示器。我们正在处理数千张图片,因此让最终用户知道发生了什么将是有价值的。 - Andrew Keller
没问题,你仍然可以从同一线程更新控制台;用户只能使用Control-C退出,但这正是你要寻找的行为。 - Andrew Barber
2
使用异步操作也可以在需要下载多个文件等情况下带来好处...不确定这里的理由是什么,但如果只有一个文件,同步操作会更容易。 - Reed Copsey
@Reed 我应该更清楚地表明我假设他只是在处理单个文件,是的。谢谢,Reed!+1 - Andrew Barber

1
需要说明的是,在C# 7.1中,Main现在可以标记为async
static async Task Main()
{
  await LongRunningOperation();
}

4
这个调用不能防止控制台退出。 - Jeferson Tenorio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接