Asynchronous methods in using statement
Asked Answered
B

2

26

Note: I'm using C# in Unity, that means version .NET 3.5, so I cannot use await or async keyword..

What will happen to using statement when I put a method in it which works asynchronously?

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

As you know, after the method DownloadFileAsync() is called, SomeMethod1() will be called which is out of the using block while DownloadFileAsync() is still working. So now I'm really confused what would happen to the using statement and the asynchronous method in this case.

Would Dispose() of wc be called at the right time without any problems?

If not, how do I correct this example?

Belindabelisarius answered 15/11, 2015 at 17:55 Comment(5)
This will not work. Use await with *TaskAsyncBurkhart
@JasonEvans, why do you say yes? since OP is not using await, then there is a problem.Paunch
Thank you so much for the replies, I made this stupid mistakes several times.. Then how do I avoid this? Just add await keyword?Belindabelisarius
Since the OP isn't using an async method that returns a Task, the Mjolnir'ed duplicate is less than useful. The OP of the duplicate was fortuitous in that they didn't know they just needed to use async/await with their method. To @JenixGuy, if you're able to use WebClient.DownloadFileTaskAsync() you can use what's proposed in the duplicate.Ondometer
Just add await keyword if you knew what await did the answer would be obvious to you. Don't add keywords to your code when you are not sure what they do.Blen
C
22

From the comments:

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

No, you can't just do that. (And that's why the previously proposed duplicate question was not in fact a duplicate…your scenario is subtly different.) You will need to delay the dispose until the download has completed, but this is complicated by your need to execute two more program statements (at least…it's impossible to know for sure without a good, minimal, complete code example).

I do think you should switch to the awaitable WebClient.DownloadFileTaskAsync() method, as this will at least simplify the implementation, making it simple to retain the using statement.

You can address the other part of the problem by capturing the returned Task object and not awaiting it until after your other program statements have executed:

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

In this way, the download can be started, your other two methods called, and then the code will wait for the completion of the download. Only when it's completed will the using block then be exited, allowing the WebClient object to be disposed.

Of course, in your current implementation you undoubtedly are handling an appropriate DownloadXXXCompleted event. If you want, you can continue using the object that way. But IMHO once you have switched over to using await, it's much better to just put after the await the code that needs to execute on the completion of the operation. This keeps all of the code relevant to the operation in one place and simplifies the implementation.


If for some reason you can't use await, then you will have to use some alternate mechanism for delaying the dispose of the WebClient. Some approaches will allow you to continue to use using, others will require that you call Dispose() in the DownloadXXXCompleted event handler. Without a more complete code example, and a clear explanation for why await is not suitable, it would not be possible to say for sure what the best alternative would be.


EDIT:

Since you've confirmed that you don't have access to await in the current code, here are a couple of other options compatible with older code…

One possibility is to just wait in the same thread after starting the operation:

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);
    }
}

(Note: one can use any suitable synchronization above, such as ManualResetEvent, CountdownEvent, or even Semaphore and/or "slim" equivalents. I use Monitor simply due to its simplicity and efficiency, and take as granted readers can adjust to accommodate their preferred means of synchronization. One obvious reason one might prefer something other than Monitor is that the other types of synchronization techniques won't run the risk of having the DownloadFileCompleted event handler itself block waiting on the SomeMethod1() and SomeMethod2() methods to complete. Whether this is important depends of course on how long those method calls would take as compared to the file download.)

The above will, however, block the current thread. In some cases this may be fine, but most often the operation is being initiated in the UI thread, and that thread should not be blocked for the duration of the operation. In that case, you will want to forego using altogether and just call Dispose() from the completion event handler:

WebClient wc = new WebClient();
wc.DownloadFileCompleted += (sender, e) =>
{
    wc.Dispose();
};
wc.DownloadFileAsync(urlUri, outputFile);
SomeMethod1();
SomeMethod2();
Clayton answered 15/11, 2015 at 18:42 Comment(2)
I just found out the keyword await can't be used in version 3.5 Unity3D is using.. But, your answer is very helpful! Thank you so much!Belindabelisarius
Very good. Especially the last one is very easy and clean! :)Belindabelisarius
A
6

The System.Net.WebClient provides the event DownloadFileCompleted. You could add a handler for that event and dispose of the client at that time.

Anya answered 15/11, 2015 at 18:59 Comment(1)
Yep! This is the one of the best ways I can choose, thanks!Belindabelisarius

© 2022 - 2024 — McMap. All rights reserved.