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 ?
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 WinForms
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 Console applications
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.
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.
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!
Related
Update
I'm now wondering if the fact that the IEnumerable that the foreach loop iterates over here is from a loop that uses yield return. I'm not sure if this has any effect on threading... ?
Can anyone point out why I never see the BackgroundWorker RunWorkerCompleted event fire in this code (this is a .NET 4, MVC 3 application)?
Regardless of what I set for, WorkerSupportsCancellation and WorkerReportsProgress the completed events never seem to fire.
Even when I try throwing an exception in the DoWork block, I never see the completed event. As I understand it, I should.
Anything obvious here?
By the way, I'm unable to upgrade the project to .NET 4.5 to use newer async features due to project restrictions.
var autoResetEvent = new AutoResetEvent(false);
UploadsExpected = pagesFound;
foreach (var rasterisedPageFilePath in rasterisedPageFilePathList)
{
// Track uploads
UploadsStarted += 1;
int uploadCount = UploadsStarted;
// Track concurrent uploads in main thread and while we are at our
// maximum, pause and wait before starting the next upload thread
while (ConcurrentUploadsRunning >= maxConcurrentUploads)
{
Debug.WriteLine(string.Format("Waiting to start upload: {0}",
uploadCount));
Thread.Sleep(3000);
}
ConcurrentUploadsRunning += 1;
Debug.WriteLine(string.Format("Initiating new upload: {0}", uploadCount));
// Create a background worker so we can run the upload asynchronously.
var backgroundWorker = new BackgroundWorker();
// Set up anonymous method that runs asynchronously
backgroundWorker.DoWork += (sender, e) =>
{
try
{
var storageManager = new storageManager(awsS3BucketName);
string imgFilePath = (string) e.Argument;
using (var fileStream = new FileStream(imgFilePath, FileMode.Open))
{
storageManager.Save(Path.GetFileName(imgFilePath),
MimeTypes.JPEG, fileStream);
}
}
catch (Exception ex)
{
UploadHasFailed = true;
m_logManager.Fatal("Upload of file to AWS S3 has failed", ex);
}
// Run check for AutoResetEvent following Save complete,
// and if the completed uploads count indicates that all uploads
// have finished, set AutoResetEvent so main thread can exit
if ((UploadsCompleted += 1) == UploadsExpected)
{
autoResetEvent.Set();
}
Debug.WriteLine(string.Format("Upload complete: {0}", uploadCount));
ConcurrentUploadsRunning -= 1;
};
backgroundWorker.RunWorkerCompleted += (sender, args) =>
{
// Never fires
};
backgroundWorker.ProgressChanged += (sender, args) =>
{
// Never fires
};
backgroundWorker.RunWorkerAsync(rasterisedPageFilePath);
}
autoResetEvent.WaitOne();
try
{
inputStream.Close();
} catch { }
There are many strange quirks in this code - is the thread executing the code above your main thread or some other thread? Either way, if it's your GUI thread then it's blocking (.Sleep(3000)), if it isn't, then you create your backgroundworker in the wrong context
I have the following code that will download a file asynchronously to my hard-drive, shouting to the console the current progress and quitting with a goodbye message in the end:
webClient.DownloadProgressChanged.Add(fun args ->
if (currentPercentage < args.ProgressPercentage) then
Console.WriteLine(args.ProgressPercentage.ToString() + "%")
currentPercentage <- args.ProgressPercentage
)
webClient.DownloadFileCompleted.Add(fun args ->
Console.WriteLine("Download finished!")
Environment.Exit 0
)
webClient.DownloadFileAsync(new Uri(url_to_download), file_name)
Thread.Sleep Int32.MaxValue
I was wondering, though, whether there could be any more elegant way of achieving this without having to resort to "sleeping forever" in the main thread, having the program end though an Environment.Exit(). I have no prejudice towards using Environment.Exit() but I'd like to avoid it, if possible! The only way I can think of avoiding this would be to spawn a new thread and then waiting for it to die, but that does seem cumbersome. Any simpler way to accomplish this?
You can use a ResetEvent like this:
webClient.DownloadProgressChanged += (f,a) => ...
AutoResetEvent resetEvent = new AutoResetEvent(false);
webClient.DownloadFileCompleted += (f, a) => resetEvent.Set();
webClient.DownloadDataAsync(new Uri(url_to_download), file_name);
resetEvent.WaitOne();
Console.WriteLine("Finished");
simply use a waithandle derived class like the mutex to signal you're ready to close down. signal it in your download completed method and wait for it at the end of your app. as it becomes signalled your app will exit naturally.
In case you are a fan of the Reactive extensions library (Rx), then this process can be modeled in terms of observable like:
public static IObservable<int> DownloadURL(string url,string fname)
{
return Observable.Defer(() =>
{
var sub = new Subject<int>();
var wc = new WebClient();
wc.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
sub.OnNext(e.ProgressPercentage);
if (e.ProgressPercentage == 100)
sub.OnCompleted();
};
wc.DownloadFileAsync(new Uri(url), fname);
return sub;
});
}
public static void Main(string[] str)
{
foreach (var i in DownloadURL("http://www.google.com", "g:\\google.html").DistinctUntilChanged().ToEnumerable())
Console.WriteLine(i);
}
In C# you could write an extension method for WebClient that waits for the download to complete, while still pitching update events:
static class WebClientEx {
public static void DownloadSemiSync(this WebClient webClient, Uri address, string filename) {
var evt = new AutoResetEvent(false);
webClient.DownloadFileCompleted += (s, e) => evt.Set();
webClient.DownloadFileAsync(address, filename);
evt.WaitOne();
}
}
This'd allow you to define whatever progress event you want on it and then use it as a synchronous function, reducing your main code to this:
static void Main() {
var webClient = new WebClient();
webClient.DownloadProgressChanged += (s, args) => {..};
webClient.DownloadSemiSync(new Uri("http://.."), "test.bin");
Console.WriteLine("DownloadFinished");
}
Throws all the events, but then waits to exit.
I use WebBrowser Control in c# to check a couple search engine results but sometime for no reason it gets stuck. My first thought was to make each searching function as a thread and use thread.abort() from a timer but I just couldn't handle with the UI Controls (including the WebBrowser) no matter what I've tried.
Anyone has a solution for me? an example would be great cause I already tried so many things and I keep getting all these exceptions.
I believe you can use WebRequest in a Background worker & avoid the difficulties of dealing with the COM threading model. You can use WebRequest.Timeout to handle anything you think is taking too long.
Try something like this:
static void Main(string[] args)
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += backgroundWorker_DoWork;
bg.RunWorkerAsync(new List<object> { "http://download.thinkbroadband.com/512MB.zip" });
while (true) {}
}
private static void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Starting operation.");
BackgroundWorker bw = sender as BackgroundWorker;
List<object> args = (List<object>)e.Argument;
var url = (string)args[0];
WebRequest request = WebRequest.Create(url);
request.Timeout = 300;
try
{
WebResponse response = request.GetResponse();
Console.WriteLine("Request successful.");
}
catch (Exception ex)
{
Console.WriteLine("Request timed out.");
}
}
WebBrowser is a component you should use when you want to embed an instance of IE into your presentation layer. Unless you need this, you can use something more lightwweight.
Use the control's .Invoke() method: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
There are other methods you can use too: .BeginInvoke and .EndInvoke for asynchronous invokes.
if(control.InvokeRequired)
{
control.Invoke(control.delegate);
}
I have this code:
public static String Download(string address) {
WebClient client = new WebClient();
Uri uri = new Uri(address);
// Specify a progress notification handler.
client.DownloadProgressChanged += (_sender, _e) => {
//
};
// ToDo: DownloadStringCompleted event
client.DownloadStringAsync(uri);
}
Instead of having the rest of my code execute in the DownloadStringCompleted event handler when the download is complete, can I somehow Join this Async request? It will be housed in another thread (doing it this way so I have access to the download progress). I know that DownloadStringAsync can take a second parameter; an object called userToken in the manual. Could this be of use? Thank you,
You could use a manual reset event:
class Program
{
static ManualResetEvent _manualReset = new ManualResetEvent(false);
static void Main()
{
WebClient client = new WebClient();
Uri uri = new Uri("http://www.google.com");
client.DownloadProgressChanged += (_sender, _e) =>
{
//
};
client.DownloadStringCompleted += (_sender, _e) =>
{
if (_e.Error == null)
{
// do something with the results
Console.WriteLine(_e.Result);
}
// signal the event
_manualReset.Set();
};
// start the asynchronous operation
client.DownloadStringAsync(uri);
// block the main thread until the event is signaled
// or until 30 seconds have passed and then unblock
if (!_manualReset.WaitOne(TimeSpan.FromSeconds(30)))
{
// timed out ...
}
}
}
My first thought would be to use DownloadString, the synchronous version of DownloadStringAsync. However, it seems that you must use an async method to get the progress notification. Okay, that's no big deal. Just subscribe to DownloadStringCompleted and use a simple wait handle like ManualResetEventSlim to block until it completes.
One note, I am not sure if the progress notification is even raised for DownloadStringAsync. According to MSDN, DownloadProgressChanged is associated with some of the async methods, but not DownloadStringAsync.
I have the following method:
public void PutFile(string ID, Stream content)
{
try
{
ThreadPool.QueueUserWorkItem(o => putFileWorker(ID, content));
}
catch (Exception ex)
{
OnPutFileError(this, new ExceptionEventArgs { Exception = ex });
}
}
The putFileWorker method looks like this:
private void putFileWorker(string ID, Stream content)
{
//Get bucket name:
var bucketName = getBucketName(ID)
.ToLower();
//get file key
var fileKey = getFileKey(ID);
try
{
//if the bucket doesn't exist, create it
if (!Amazon.S3.Util.AmazonS3Util.DoesS3BucketExist(bucketName, s3client))
s3client.PutBucket(new PutBucketRequest { BucketName = bucketName, BucketRegion = S3Region.EU });
PutObjectRequest request = new PutObjectRequest();
request.WithBucketName(bucketName)
.WithKey(fileKey)
.WithInputStream(content);
S3Response response = s3client.PutObject(request);
var xx = response.Headers;
OnPutFileCompleted(this, new ValueEventArgs { Value = ID });
}
catch (Exception e)
{
OnPutFileError(this, new ExceptionEventArgs { Exception = e });
}
}
I've created a little console app to test this.
I wire up event handlers for the OnPutFileError and OnPutFileCompleted events.
If I call my PutFile method, and step into this, it gets to the "//if the bucket doesn't exist, create it" line, then exits. No exception, no errors, nothing.
It doesn't complete (i've set breakpoints on my event handlers too) - it just exits.
If I run the same method without the ThreadPool.QueueUserWorkItem then it runs fine...
Am I missing something?
ThreadPool threads are background threads (see the link). They will not keep your application running if the main thread exits.
Typically, in WinForms apps, this is not a problem, because the main UI thread calls Application.Run and starts processing events. For your console app, if your Main method doesn't wait for the work item to complete somehow, the main thread will queue the work item and then exit.
You could create a background thread yourself and set its IsBackground property to false. Or you could create a thread and call Thread.Join to wait for it to finish.
-- EDIT --
As suggested in the comments below, you could also use a ManualResetEvent, or even a custom synchronization class as suggested by Linik. The goal is to block the main thread until the the background threads have completed.
To use a ManualResetEvent, create it in your main thread and pass it in as an argument. (I'll assign it to a static variable here just for brevity.)
ManualResetEvent s_WaitEvent;
ManualResetEvent s_WaitEvent = new ManualResetEvent(false); // non-signaled
// queue work item here
s_WaitEvent.WaitOne();
At the end of your worker thread, signal the event:
s_WaitEvent.Set();
Link's CountDownLatch is nice if you have many threads that must process before you can exit. You can also use separate ManualResetEvents for each thread and wait for them all to complete using WaitHandle.WaitAll(WaitHandle[]). (ManualResetEvent inherits from WaitHandle.)
Put a Console.ReadLine() in your Main thread to block it while you test your worker thread. This will keep main from exiting. Just hit enter when you're done.
Use a CountDownLatch to force the main to wait for all of the threads that you have queued up:
public class CountDownLatch
{
private int m_remain;
private EventWaitHandle m_event;
public CountDownLatch (int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException();
m_remain = count;
m_event = new ManualResetEvent(false);
if (m_remain == 0)
{
m_event.Set();
}
}
public void Signal()
{
// The last thread to signal also sets the event.
if (Interlocked.Decrement(ref m_remain) == 0)
m_event.Set();
}
public void Wait()
{
m_event.WaitOne();
}
}
In Main:
static void Main(string[] args)
{
CountDownLatch latch = new CountDownLatch(numFiles);
//
// ...
//
putFileWorker("blah", streamContent);
//
// ...
//
// waits for all of the threads to signal
latch.Wait();
}
In the worker method:
private void putFileWorker(string ID, Stream content)
{
try
{
//Get bucket name:
var bucketName = getBucketName(ID)
.ToLower();
//get file key
var fileKey = getFileKey(ID);
try
{
//if the bucket doesn't exist, create it
if (!Amazon.S3.Util.AmazonS3Util.DoesS3BucketExist(bucketName, s3client))
s3client.PutBucket(new PutBucketRequest { BucketName = bucketName, BucketRegion = S3Region.EU });
//
// ...
//
}
catch (Exception e)
{
OnPutFileError(this, new ExceptionEventArgs { Exception = e });
}
}
finally
{
latch.Signal();
}
}