在 using 语句中使用异步方法

26
注意:我正在使用C#在Unity中,这意味着版本为.NET 3.5,因此我不能使用await或async关键字。
当我将一个异步工作的方法放在using语句中时,会发生什么?
using (WebClient wc = new WebClient()) {
    wc.DownloadFileAsync(urlUri, outputFile);
}
SomeMethod1();
SomeMethod2();

就像你所知道的,在调用方法DownloadFileAsync()之后,SomeMethod1()将被调用,它位于using块之外,而DownloadFileAsync()仍在工作中。所以现在我真的很困惑,在这种情况下using语句和异步方法会发生什么。

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

在没有任何问题的情况下,在正确的时间是否会调用wcDispose()呢?


3
这样行不通。请使用*TaskAsyncawait - SLaks
1
@JasonEvans,你为什么说是的?因为OP没有使用await,所以存在问题。 - Yacoub Massad
非常感谢您的回复,我犯了这些愚蠢的错误好几次了...那么我该如何避免这种情况呢?只需添加await关键字吗? - Jenix
1
由于OP没有使用返回“Task”的异步方法,因此Mjolnir'ed重复内容不太有用。 重复的OP很幸运,因为他们不知道只需要在方法中使用async / await即可。 对于@JenixGuy,如果您能够使用WebClient.DownloadFileTaskAsync(),则可以使用重复中提出的内容。 - jdphenix
1
如果你知道await的作用,答案就很明显了,只需添加await关键字即可。当你不确定关键字的作用时,请勿将其添加到代码中。 - usr
2个回答

22

根据评论:

那我怎么避免这个问题?只需要添加await关键字吗?

不,你不能仅仅这样做。(这就是为什么之前提出的重复问题实际上并不是重复的…你的情况略有不同。) 你需要延迟处理(dispose)直到下载完成,但由于你需要执行另外两条程序语句(至少如此…如果没有好的、最小化的完整的代码示例是无法确定的)这会变得很麻烦。

我认为你应该转换为可等待(async-await)的 WebClient.DownloadFileTaskAsync() 方法,这将简化实现过程,使保持 using 语句成为可能。

你可以通过捕获返回的Task对象,并在其他程序语句执行再进行等待(await),来解决问题的另一个部分:

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

通过这种方式,下载可以开始,您的其他两种方法被调用,然后代码将等待下载完成。只有在下载完成后,using块才会退出,允许WebClient对象被处理。

当然,在您当前的实现中,您无疑正在处理适当的DownloadXXXCompleted事件。如果您愿意,您仍然可以继续使用该对象。但我认为,一旦您转而使用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和/或“slim”等效类。我之所以使用Monitor,只是因为它简单高效,并且假设读者可以根据自己的喜好调整来适应自己的同步手段。显然,一个人可能更喜欢使用Monitor以外的某些同步技术,原因是其他类型的同步技术不会出现DownloadFileCompleted事件处理程序本身阻塞等待SomeMethod1()SomeMethod2()方法完成的风险。当然,这是否重要取决于这些方法调用需要多长时间才能与文件下载相比较。)

然而,上述方法将阻塞当前线程,在某些情况下这可能是可以接受的,但往往操作是在UI线程中启动的,该线程在操作期间不应被阻塞。在这种情况下,您将想放弃using,并仅从完成事件处理程序中调用Dispose()

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

我刚刚发现 await 关键字不能在 Unity3D 版本3.5 中使用。但是,你的回答非常有帮助!非常感谢! - Jenix
1
非常好。特别是最后一个非常简单而且干净! :) - Jenix

6
< p > System.Net.WebClient提供了DownloadFileCompleted事件。您可以为该事件添加处理程序,并在此时处理客户端的释放。

没错!这是我可以选择的最佳方式之一,谢谢! - Jenix

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