using 语句中的异步方法

Posted

技术标签:

【中文标题】using 语句中的异步方法【英文标题】:Asynchronous methods in using statement 【发布时间】:2015-11-15 17:55:00 【问题描述】:

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

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

using (WebClient wc = new WebClient()) 
    wc.DownloadFileAsync(urlUri, outputFile);

SomeMethod1();
SomeMethod2();

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

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

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

【问题讨论】:

这行不通。使用 await*TaskAsync @JasonEvans,你为什么说是?由于OP没有使用await,那么就有问题了。 非常感谢您的回复,我犯了好几次这种愚蠢的错误。那我该如何避免呢?只需添加 await 关键字? 由于 OP 没有使用返回 Task 的异步方法,因此 Mjolnir 的副本不太有用。副本的 OP 是偶然的,因为他们不知道他们只需要在他们的方法中使用 async/await。致@JenixGuy,如果您能够使用WebClient.DownloadFileTaskAsync(),您可以使用副本中建议的内容。 Just add await keyword 如果你知道 await 做了什么,答案对你来说是显而易见的。当您不确定它们的作用时,不要在代码中添加关键字。 【参考方案1】:

来自cmets:

那我该如何避免呢?加个 await 关键字就行了?

不,你不能只是这样做。 (这就是为什么先前提出的重复问题实际上不是重复的……您的情况略有不同。)您需要延迟处理直到下载完成,但这会因为您需要执行另外两个程序语句而变得复杂(至少……如果没有a good, minimal, complete code example,就不可能确定。

确实认为您应该切换到可等待的WebClient.DownloadFileTaskAsync() 方法,因为这至少会简化实现,使保留using 语句变得简单。

您可以通过捕获返回的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 和/或“slim”等价物。我使用Monitor 仅仅是因为它的简单性和效率,并认为读者可以调整以适应他们喜欢的同步方式。一个明显的原因可能是比Monitor 更喜欢other的东西,其他类型的同步技术不会运行让DownloadFileCompleted 事件处理程序本身阻塞等待SomeMethod1()SomeMethod2() 方法完成的风险。这是否重要当然取决于这些方法调用与文件下载相比需要多长时间。)我>

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

WebClient wc = new WebClient();
wc.DownloadFileCompleted += (sender, e) =>

    wc.Dispose();
;
wc.DownloadFileAsync(urlUri, outputFile);
SomeMethod1();
SomeMethod2();

【讨论】:

我刚刚发现Unity3D正在使用的3.5版本中不能使用关键字await。但是,您的回答非常有帮助!非常感谢! 非常好。特别是最后一个非常简单干净! :)【参考方案2】:

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

【讨论】:

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

以上是关于using 语句中的异步方法的主要内容,如果未能解决你的问题,请参考以下文章

调试器在 Visual Studio 中的异步 HttpClient.GetAsync() 调用后停止

seajs.use为异步加载吗

C# 异步编程

EF6基础系列(十一)---EF6中的异步查询和异步保存

异步上传和调整多个图像大小时出现内存不足异常

在没有等待 #2 的情况下调用异步方法