using语句中的异步方法
Asynchronous methods in using statement
注意:我在 Unity 中使用 C#,这意味着版本 .NET 3.5,所以我不能使用 await
或 async
关键字。
当我在其中放入一个可以异步工作的方法时,using 语句会发生什么?
using (WebClient wc = new WebClient()) {
wc.DownloadFileAsync(urlUri, outputFile);
}
SomeMethod1();
SomeMethod2();
如你所知,调用方法DownloadFileAsync()
后,SomeMethod1()
将被调用,它在using
块之外,而DownloadFileAsync()
仍然是[=29] =]工作。所以现在我真的很困惑在这种情况下 using 语句和异步方法会发生什么。
wc
的 Dispose()
会在正确的时间调用而没有任何问题吗?
如果不是,我该如何更正这个例子?
来自评论:
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);
}
}
(注意:可以使用上面任何合适的同步,例如ManualResetEvent
、CountdownEvent
,甚至Semaphore
and/or "slim" 等价物。我使用 Monitor
仅仅是因为它的简单性和效率,并且理所当然地读者可以调整以适应他们喜欢的同步方式。一个明显的原因是人们可能更喜欢 other 与 Monitor
相比,其他类型的同步技术不会 运行 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
。您可以为该事件添加处理程序并在那时处理客户端。
注意:我在 Unity 中使用 C#,这意味着版本 .NET 3.5,所以我不能使用 await
或 async
关键字。
当我在其中放入一个可以异步工作的方法时,using 语句会发生什么?
using (WebClient wc = new WebClient()) {
wc.DownloadFileAsync(urlUri, outputFile);
}
SomeMethod1();
SomeMethod2();
如你所知,调用方法DownloadFileAsync()
后,SomeMethod1()
将被调用,它在using
块之外,而DownloadFileAsync()
仍然是[=29] =]工作。所以现在我真的很困惑在这种情况下 using 语句和异步方法会发生什么。
wc
的 Dispose()
会在正确的时间调用而没有任何问题吗?
如果不是,我该如何更正这个例子?
来自评论:
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);
}
}
(注意:可以使用上面任何合适的同步,例如ManualResetEvent
、CountdownEvent
,甚至Semaphore
and/or "slim" 等价物。我使用 Monitor
仅仅是因为它的简单性和效率,并且理所当然地读者可以调整以适应他们喜欢的同步方式。一个明显的原因是人们可能更喜欢 other 与 Monitor
相比,其他类型的同步技术不会 运行 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
。您可以为该事件添加处理程序并在那时处理客户端。