using语句中的异步方法

Asynchronous methods in using statement

注意:我在 Unity 中使用 C#,这意味着版本 .NET 3.5,所以我不能使用 awaitasync 关键字。

当我在其中放入一个可以异步工作的方法时,using 语句会发生什么?

using (WebClient wc = new WebClient()) {
    wc.DownloadFileAsync(urlUri, outputFile);
}
SomeMethod1();
SomeMethod2();

如你所知,调用方法DownloadFileAsync()后,SomeMethod1()将被调用,它在using块之外,而DownloadFileAsync()仍然是[=29] =]工作。所以现在我真的很困惑在这种情况下 using 语句和异步方法会发生什么。

wcDispose() 会在正确的时间调用而没有任何问题吗?

如果不是,我该如何更正这个例子?

来自评论:

Then how do I avoid this? Just add await keyword?

不,你不能只是那样做。 (这就是为什么之前提出的重复问题实际上不是重复的......你的场景略有不同。)你需要延迟处理直到下载完成,但由于你需要执行两个以上的程序语句(至少……没有 a good, minimal, complete code example).

就不可能确定

认为你应该切换到可等待的WebClient.DownloadFileTaskAsync()方法,因为这至少会简化实现,使保留[=14=变得简单]声明。

您可以通过捕获返回的 Task 对象来解决问题的另一部分,并且在您的其他程序语句执行 之后才等待它:

using (WebClient wc = new WebClient()) {
    Task task = wc.DownloadFileTaskAsync(urlUri, outputFile);
    SomeMethod1();
    SomeMethod2();
    await task;
}

这样就可以开始下载了,你另外两个方法调用了,然后然后代码会等待下载完成。只有当它完成时才会退出 using 块,从而允许释放 WebClient 对象。

当然,在您当前的实现中,您无疑正在处理适当的 DownloadXXXCompleted 事件。如果需要,您可以继续以这种方式使用该对象。但是恕我直言,一旦您切换到使用 await,最好将需要在操作完成时执行的代码放在 await 之后。这将所有与操作相关的代码都放在一个地方并简化了实现。


如果由于某种原因你不能使用 await,那么你将不得不使用一些替代机制来延迟 WebClient 的处理。有些方法将允许您继续使用 using,其他方法将要求您在 DownloadXXXCompleted 事件处理程序中调用 Dispose()。如果没有更完整的代码示例,以及为什么 await 不合适的明确解释,则无法确定最佳替代方案是什么。


编辑:

由于您已确认您无法访问当前代码中的 await,这里有几个与旧代码兼容的其他选项…

一种可能是在开始操作后在同一个线程中等待:

using (WebClient wc = new WebClient()) {
    object waitObject = new object();
    lock (waitObject)
    {
        wc.DownloadFileCompleted += (sender, e) =>
        {
            lock (waitObject) Monitor.Pulse(waitObject);
        };
        wc.DownloadFileAsync(urlUri, outputFile);
        SomeMethod1();
        SomeMethod2();
        Monitor.Wait(waitObject);
    }
}

(注意:可以使用上面任何合适的同步,例如ManualResetEventCountdownEvent,甚至Semaphore and/or "slim" 等价物。我使用 Monitor 仅仅是因为它的简单性和效率,并且理所当然地读者可以调整以适应他们喜欢的同步方式。一个明显的原因是人们可能更喜欢 otherMonitor 相比,其他类型的同步技术不会 运行 DownloadFileCompleted 事件处理程序本身阻塞等待 SomeMethod1() 和 [=35] 的风险=] 方法来完成。这是否重要当然取决于与文件下载相比这些方法调用需要多长时间。)

然而,以上将阻塞当前线程。在某些情况下,这可能没问题,但大多数情况下,操作是在 UI 线程中启动的,并且该线程不应在操作期间被阻塞。在这种情况下,您将希望完全放弃 using,而只需从完成事件处理程序中调用 Dispose()

WebClient wc = new WebClient();
wc.DownloadFileCompleted += (sender, e) =>
{
    wc.Dispose();
};
wc.DownloadFileAsync(urlUri, outputFile);
SomeMethod1();
SomeMethod2();

System.Net.WebClient提供事件DownloadFileCompleted。您可以为该事件添加处理程序并在那时处理客户端。