Guillermo Mestre
Guillermo Mestre

Reputation: 490

How to launch a Thread inside a BackgroundWorker?

My application has to connect to an API and download around 1200 items. Because how the API works, I can't ask for all the items at once, I have to send one query for each item. Items are small, so the downloads are quick, and the server blocks me (for 4-5 seconds) if I send too many requests in a short amount of time (100 requests per 2-3 seconds). I don't want the server blocking my app around 10-12 times each time, so I decided to implement a slowdown on purpose, another thread that simply sleeps for 0.4 seconds, and the worker will join it, so if the worker finishes each download before the 0.4s mark it will wait, and if it finishes latter, it will proceed directly to the next

Desired implementation:

private void DownloadLibrary_DoWork(object sender, DoWorkEventArgs e)
{
    int max = 1200;
    int slowdown = 400; // milliseconds

    Thread methronome = new Thread(() => Thread.Sleep(slowdown));

    for (int i = 0; i < max; i++)
    {
        methronome.Start();

        DownloadItem(i);            
        WorkerDownloadLibrary.ReportProgress((int)i / max, string.Format("Dowloaded item {0} out of {1}", i+1, max));

        methronome.Join();
    }
}

private void UserClickStart(object sender, RoutedEventArgs e)
{
    System.Windows.MessageBox.Show("Let's start!");
    WorkerDownloadLibrary.RunWorkerAsync();
}

private void DownloadLibrary_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{ 
    System.Windows.MessageBox.Show("Completed!"); 
}

The problem is that, if I do this, the background worker ends prematurely (RunWorkerCompleted executes), and if I comment both the methronome.Start() and methronome.Join() lines, it do the work as intended, but of course ends up being blocked from time to time. What I'm doing wrong?


I can implement the wait directly on the backgroundworker because the downloads are fast enough anyway, but still seems bad designed, and it will slow it too much on a slow computer.

Working but slow implementation:

private void DownloadLibrary_DoWork(object sender, DoWorkEventArgs e)
{
    int max = 1200;
    int slowdown = 400; // milliseconds

    for (int i = 0; i < max; i++)
    {
        DownloadItem(i);            
        WorkerDownloadLibrary.ReportProgress((int)i / max, string.Format("Dowloaded item {0} out of {1}", i+1, max));
        Thread.Sleep(slowdown);
    }
}

There is another way to do it: Send all the queries one after the other, and wait only when the server blocks, but seems very bad by design, and the user will scratch its head when the download stops each 2 seconds. And the server may block me permanently if I do this always

Working but plain bad implementation

private void DownloadLibrary_DoWork(object sender, DoWorkEventArgs e)
{
    int max = 1200;
    int slowdown = 5000; // milliseconds

    string serverResponse;
    for (int i = 0; i < max; i++)
    {
        do
        {
            serverResponse = DownloadItem(i);

            if (serverResponse != "OK")
                Thread.Sleep(slowdown);

        } while (serverResponse != "OK") ;

        WorkerDownloadLibrary.ReportProgress((int)i / max, string.Format("Dowloaded item {0} out of {1}", i+1, max));
    }
}

How can I implement the first option?

Upvotes: 2

Views: 942

Answers (1)

BartoszKP
BartoszKP

Reputation: 35901

Your approach doesn't work because, as the documentation explains:

Once the thread terminates, it cannot be restarted with another call to Start.

So, the simplest fix is to recreate the thread each time:

for (int i = 0; i < max; i++)
{
    Thread methronome = new Thread(() => Thread.Sleep(slowdown));
    methronome.Start();

   //..

However, it is unclear why would you want to do it. The code runs in another thread already, so your second approach, using Thread.Sleep seems fine. You could improve it by measuring if you really need to wait the whole amount of time (perhaps some downloading last a little longer so there is no need to always wait all 400 ms). A convenient class that can be used to measure chunks of time (and unlike threads, can be "restarted") is the Stopwatch class. The idea (posted also by jessehouwing in the comments) would look something like:

int max = 1200;
int slowdownMs = 400;
Stopwatch sw = new Stopwatch();

for (int i = 0;i < max; ++i)
{
    sw.Restart();

    DownloadItem(i);    
    ReportProgress(i, max);        

    sw.Stop();

    Thread.Sleep(Math.Max(0, (int)(slowdownMs - sw.ElapsedMilliseconds)));
}

//....

Upvotes: 2

Related Questions