Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mono's BackgroundWorker not working in background?

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 BackgroundWorkers 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 BackgroundWorkers 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 BackgroundWorkers 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 BackgroundWorkers running concurrently? Is there some parameter I need to set?

like image 252
Tom Avatar asked Jan 17 '23 19:01

Tom


1 Answers

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.

like image 149
Christopher Currens Avatar answered Jan 19 '23 09:01

Christopher Currens