I have a predicament with Mono. I'm trying to understand how or why BackgroundWorker
instances don't run concurrently, or at least do not do so very efficiently or quickly. It's almost as if the number of threads is being limited somehow, but I'm not much of a Mono developer or .NET developer, so I throw myself on the mercy of my SO brethren.
Consider the following example:
using System;
using System.ComponentModel;
using System.Threading;
namespace backgroundworkertest
{
class MainClass
{
public static void foo(object sender, DoWorkEventArgs e) {
Console.WriteLine("Start");
Thread.Sleep(500);
Console.WriteLine("Done");
}
public static void Main (string[] args)
{
for(int i = 0; i < 6; i++) {
BackgroundWorker b = new BackgroundWorker();
b.WorkerReportsProgress = false;
b.DoWork += new DoWorkEventHandler(foo);
b.RunWorkerAsync();
}
Thread.Sleep(2000);
Console.WriteLine("Really done");
}
}
}
When I run the above, I get the following output.
Start Start Done Start Start Done Start Done Really Done
First of all, not all of the BackgroundWorker
s complete within two whole seconds. From the loop, it's clear I'm instantiating 6 workers, but only 3 print "Done". I feel like two seconds should be long enough for these workers to complete if all 6 were running concurrently.
The real inside story is that I have an app that runs just fine (using a significant number of BackgroundWorker
s that fetch images via HTTP in the background. However, the difference is that this app is implemented in C# .NET. When I port the app to Mono, the BackgroundWorker
s only seem to be running a couple at a time, and the HTTP connections are prone to timing out as a result. I wanted to see if this was the case, hence the example above and the question...
What's going on here? Why aren't all of the BackgroundWorker
s running concurrently? Is there some parameter I need to set?
I'm not 100% sure on the Mono implementation, but I know the Microsoft version fairly well, and I can only assume that Mono's acts in the same or a similar way.
First, to clarify, the BackgroundWorker
does, in fact, spawn .NET threads (different from native threads), it's on par with you calling ThreadPool.QueueUserWorkItem
yourself. This is a tiny detail that might help you understand why you're seeing this kind of behavior. It's important to keep in mind a thread in .NET may not actually represent a native thread. It is actually possible for all 6 of your background workers, while running on 6 .NET threads, to all run on 1-2 native threads. On my machine, only 3 native threads were actually spawned with the above code.
Secondly, you aren't considering the time it takes to construct a new BackgroundWorker
, set the delegates, then run the worker asynchronously, which may or may not involve the creation of a native thread, which is actually a considerably expensive operation. Adding a timer, in my tests, it took a total of just a little bit over a second to create all six, sometimes a little longer. Because of scheduling, the ThreadPool may take several milliseconds before it discovers that all threads are saturated/blocking (because of Thread.Sleep
), and then creates a new thread. That means, in some cases, your background worker perhaps will start around 0:01:700, where there is only 300ms left, yet the function sleeps for 500ms. Not enough time for the task to complete.
I don't think it's disputed that Mono's implementation runs a bit slower than Microsoft's, which I'm sure is the reason why you probably didn't discover that you can get the same behavior to happen with their runtime.
When in doubt, blame your own code, because that's usually where the problem is. Sleeping for how much time you expect an operation to complete, isn't really a good way to test something. What happens if other programs are running on the computer? Thread.Sleep
doesn't run slower if other process's threads have preempted yours. It will look even worse in that case.
If you were to change the code to sleep 5ms in the foo
method, and wait 20ms in the main method after creating the background workers, the same ratio you've waited, and I'm able to see the following:
Start Start Done Done Start Start Really done Press any key to continue . . .
Only two finished, and not even all of them started. I hope you're seeing the flaw in your tests now. They are running concurrently, and I'm guessing that your problem is probably somewhere else. You say you're fetching images via http in the background. Remember, if something isn't working correctly, I would say that only 1% of the time is there actually something funky going on with the runtime, the other 99% of the time is your code. Are you using synchronous or asynchronous I/O? Only have one thread handling requests? I'd start there.
EDIT:
I decided to see what performance was actually like in Mono, instead of just relying on what I was finding in the Microsoft implementation. After discovering that Mono was actually starting all 6 background works; and they all completed within the 2000ms limit! For the record, here is a comparison of results. I modified the code a bit to show what's going on, but I assure you I was getting the same results when I ran your code as-is. The "Done Creating..." message is printed right before the main thread goes into the 2000ms sleep, where the timestamps printed are the moment the background workers enter their work methods.
Windows Mono Done Creating... Done Creating... 00:00:00.0018610 00:00:00.0088775 00:00:00.0019544 Start Start 00:00:00.5074494 Start Start Done Done Done 00:00:00.5615083 00:00:00.5027995 Start Start Done 00:00:00.5028008 00:00:01.0082133 Start Start Done 00:00:01.0082900 Done Start 00:00:01.0025428 Done Start 00:00:01.0618140 00:00:01.0026164 Start Start Done Done Done Done Done Really done Really done
I ran the test on two boxes exactly the same (Dell T3400's, same hardware), one is Windows 7 x64, and the other is Ubuntu 12.04 x64. No difference. Make sure you're testing this with a release build of the executable without any debuggers attached. Also make sure you're testing it on the same hardware. It's possible, however unlikely, that it's an issue with your linux/mono installation (assuming you're running mono on linux, and not windows). Like I said before, and I think these results back it up, I don't think this is the problem. I would look elsewhere, somewhere in your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With