Reputation: 4987
I'm trying to use an AutoResetEvent object to block the thread until the async. download of a WebClient is done.
My problem is that once I call WaitOne(), the thread just locks there and VS never reaches the breakpoint in the DownloadComplete event handler method.
Here's my code
//Class used to pass arguments to WebClient's events...
public class RunArgs
{
public JobInfo jobInfo;
public int jobTotal;
public int jobIndex;
public AutoResetEvent AutoResetEventObject;
}
List<JobInfo> jl = ConfigSectionWrapper.GetAllJobs();
int jobAmount = jl.Count;
int jobIndex = 0;
RunArgs args = new RunArgs();
args.jobTotal = jl.Count;
foreach (JobInfo ji in jl)
{
if (ji.enabled == "0")
{
args.jobIndex++;
continue;
}
try
{
args.jobIndex++;
args.jobInfo = ji;
appLog.Source = ji.eventSource;
appLog.WriteEntry(string.Format("Started job {0}...", ji.jobName), EventLogEntryType.Information);
ji.fullFileName = string.Format(ji.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
ji.fullFileName = string.Format("{0}{1}", ji.downloadDirectory, ji.fullFileName);
using (WebClient wc = new WebClient())
{
AutoResetEvent notifier = new AutoResetEvent(false);
args.AutoResetEventObject = notifier;
wc.Credentials = CredentialCache.DefaultNetworkCredentials;
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...
notifier.WaitOne();
}
}
catch (Exception ex)
{
appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
DeleteFile(ji.fullFileName);
}
}
private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
RunArgs args = (RunArgs)e.UserState;
//Do things....
args.AutoResetEventObject.Set();
}
So I instantiate notifier with false in the constructor, because I don't want its status to be signaled already. Unless I'm reading MSDN wrong ?
Anything obviously wrong ?
Upvotes: 0
Views: 1302
Reputation: 4987
Here's what I ended up doing:
private AutoResetEvent notifier = new AutoResetEvent(false);
Now the main loop looks like:
foreach (JobInfo ji in jl)
{
if (ji.enabled == "0")
{
args.jobIndex++;
continue;
}
args.jobInfo = ji;
Thread t = new Thread(new ParameterizedThreadStart(startDownload));
t.Start(args);
notifier.WaitOne();
}
private void startDownload(object startArgs)
{
RunArgs args = (RunArgs)startArgs;
try
{
args.jobIndex++;
appLog.Source = args.jobInfo.eventSource;
appLog.WriteEntry(string.Format("Started job {0}...", args.jobInfo.jobName), EventLogEntryType.Information);
args.jobInfo.fullFileName = string.Format(args.jobInfo.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
args.jobInfo.fullFileName = string.Format("{0}{1}", args.jobInfo.downloadDirectory, args.jobInfo.fullFileName);
WebClient wc = new WebClient();
wc.Credentials = CredentialCache.DefaultNetworkCredentials;
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...
}
catch (Exception ex)
{
appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
DeleteFile(args.jobInfo.fullFileName);
notifier.Set();
}
}
So now the AutoResetEvent is blocking the main thread but t can successfully trigger the DownloadFileCompleteEvent. And in the DownloadFileCompleted event, I'm obviously doing a notifier.Set() too.
Thanks all!
Upvotes: 0
Reputation: 8583
WebClient
uses the AsyncOperationManager
to manage async operations. So, make sure you are aware of how these async operations get called under different scenarios.
Under a WinForm
application AsyncOperationManager
uses the WindowsFormsSynchronizationContext
. As @Michael says, the WaitOne()
call blocks the main form thread from firing the DownloadCompleted
event. In WinForms
the DownloadCompleted
is executed on the main WinForm
thread.
So, remove the notifier.WaitOne()
and it should work. DownloadCompleted
needs to be called by the main window thread (presumably, the one which you're blocking with WaitOne()
).
Under a Console
type application the AsyncOperationManager
uses System.Threading.SynchronizationContext
and the DownloadCompleted
is executed asynchronously by a thread form the threadpool.
There is no issue with calling notifier.WaitOne()
under a Console
app; and the code above works as expected.
Upvotes: 2
Reputation: 12811
I haven't found any documentation to support this, but looking at the WebClient code in Reflector, it appears that the events are raised on the main thread, not the background thread. Since your main thread is blocking when you call notifier.WaitOne()
, the event handler never gets called.
If the example you provided accurately represents your code, there's absolutely no need to use wc.DownloadFileAsync()
instead of wc.DownloadFile()
. That notifier.WaitOne()
call ultimately makes this into a synchronous operation. If you're looking for a true asynchronous operation, you'll have to do this differently.
Upvotes: 1