How can I use a BackgroundWorker in my WinForms application?

  • (2 Pages)
  • +
  • 1
  • 2

28 Replies - 9721 Views - Last Post: 14 May 2011 - 09:07 AM Rate Topic: -----

#1 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 04:31 PM

Most of the examples I found online assume simple things like adding numbers. I'm doing something a bit more different, and since this is the first time using threading, I don't know how to make this fit with my current model.

Here's my code:

private void cmbPlatform_SelectedIndexChanged(object sender, EventArgs e)
{
    UpcomingGameFinder gameFinder = new UpcomingGameFinder();

    panelGameResults.Controls.Clear();
    int yPosition = 0;

    var upcomingGames = gameFinder.FindGamesByPlatform(cmbPlatform.Text);
    foreach (var game in upcomingGames)
    {
        GameItem item = new GameItem(game.Title, 
                                     game.ReleaseDate, 
                                     game.Synopsis,
                                     game.ImageUrl,
                                     game.PageUrl);

        item.Location = new Point(0, yPosition);
        yPosition += 160;
        panelGameResults.Controls.Add(item);
    }
}



As you can guess, the FindGamesByPlatform is the nitty gritty of the time consuming functions.

Can I wrap this in a background worker, if so, how? Or do I need to do thing within the actual method itself?

Thanks for the suggestions!

Is This A Good Question/Topic? 0
  • +

Replies To: How can I use a BackgroundWorker in my WinForms application?

#2 modi123_1   User is online

  • Suitor #2
  • member icon



Reputation: 15268
  • View blog
  • Posts: 61,212
  • Joined: 12-June 08

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 05:46 PM

I would advocate hitting up 'tasks' instead.

http://msdn.microsof...ding.tasks.aspx

It's the new 4.0 way of balanced threading. Pretty slick.

The only issue is your code would have to be restructured. Say you spin up thread that is does only FindGamesByPlatform. The rest of the code would execute after that, right? The way you have it here is you are dependent on the results of that FindGamesByPlatform. You would have to throw in some code that retrieved the data after the task/thread was done and then resume where you where you left off. An event or something like that.

You might be better of optimizing the method FindGamesByPlatform. Though I am not sure what's in it to suggest help.
Was This Post Helpful? 2
  • +
  • -

#3 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 05:56 PM

D'oh! Completely forgot to post that method. :)

public IEnumerable<Game> FindGamesByPlatform(string platform)
{
    List<Game> games = new List<Game>();

    string url = GetPlatformUrl(platform);
    string html = String.Empty;

    using (var client = new WebClient())
    {
        html = client.DownloadString(url);
    }

    int startPosition = html.IndexOf("<td class=\"year\">2011</td>");
    int endPosition = html.IndexOf("<td class=\"year\">2010</td>");

    string correctHtml = html.Substring(startPosition - 55, endPosition - startPosition - 55);
    string[] separators = new string[] {"<tr"};
    string[] htmlItems = correctHtml.Split(separators,StringSplitOptions.None);

    for (int i = 1; i < htmlItems.Length; i++)
    {
        if (i == 1)
            games.Add(ParseGameHtml("<tr" + htmlItems[i]));
        else
            games.Add(ParseGameHtml(htmlItems[i]));
    }

    return games;
}


Any recommended links about Tasks besides MSDN (which is kind of lax on useful examples)?
Was This Post Helpful? 0
  • +
  • -

#4 modi123_1   User is online

  • Suitor #2
  • member icon



Reputation: 15268
  • View blog
  • Posts: 61,212
  • Joined: 12-June 08

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 06:18 PM

So what I am getting is you are snagging someone's HTML response and filtering that? You might be able to do that in regex a bit faster... How long is that taking for you to do that? You also might want to think about caching the information once you snagged it.

Man - you gotta dig a bit deeper.. task examples up the yang!

http://msdn.microsof...y/dd537609.aspx

http://msdn.microsof...tasks.task.aspx

http://msdn.microsof...ibrary/dd460693

http://msdn.microsof...askfactory.aspx
Was This Post Helpful? 1
  • +
  • -

#5 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 06:28 PM

Looks like I got some reading to do! :) Will post back with any questions, thanks for the links. I'm using HtmlAgilityPack to parse the HTML and it's really fast. The bottleneck is the actual downloading of the web page.
Was This Post Helpful? 0
  • +
  • -

#6 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 11 May 2011 - 07:20 PM

Ok, here's what I have so far:

It properly fetches the information (as before), but it still blocks the UI. I'm obviously overlooking something. :)

private void cmbPlatform_SelectedIndexChanged(object sender, EventArgs e)
{
    UpcomingGameFinder gameFinder = new UpcomingGameFinder();

    panelGameResults.Controls.Clear();
    int yPosition = 0;

    //Get the value from the UI because I cannot access UI components from another thread.
    string platform = cmbPlatform.Text;
    var task = Task<IEnumerable<Game>>.Factory.StartNew(() => gameFinder.FindGamesByPlatform(platform));
    var upcomingGames = task.Result;

    foreach (var game in upcomingGames)
    {
        GameItem item = new GameItem(game.Title, 
                                     game.ReleaseDate, 
                                     game.Synopsis,
                                     game.ImageUrl,
                                     game.PageUrl);

        item.Location = new Point(0, yPosition);
        yPosition += 160;
        panelGameResults.Controls.Add(item);
    }

    panelGameResults.Focus();
}


Was This Post Helpful? 0
  • +
  • -

#7 JackOfAllTrades   User is offline

  • Saucy!
  • member icon

Reputation: 6258
  • View blog
  • Posts: 24,026
  • Joined: 23-August 08

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 04:06 AM

using (var client = new WebClient())
{
    html = client.DownloadString(url);
}

int startPosition = html.IndexOf("<td class=\"year\">2011</td>");
int endPosition = html.IndexOf("<td class=\"year\">2010</td>");


Dude...where's your error handling?
Was This Post Helpful? 0
  • +
  • -

#8 modi123_1   User is online

  • Suitor #2
  • member icon



Reputation: 15268
  • View blog
  • Posts: 61,212
  • Joined: 12-June 08

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 07:42 AM

Here's an example of how you would set it up. I am going to probably turn this into a tutorial in a few days. In this example class1 would be what you current class is. The secondary class, Class_TaskFoo, is where you want to push all your code you want to operate on the other thread. In your case:

UpcomingGameFinder gameFinder = new UpcomingGameFinder();
gameFinder.FindGamesByPlatform(cmbPlatform.Text);



Now the question is how to resume your main thread's operation until the thread is done. You can test with the while loop I have commented out. That would still gum up the UI though. I would say take the rest of your code and move it to a custom event coming out of the Class_TaskFoo. When Class_TaskFoo's start method is done it would raise an event like "done!".. the main thread catches that and then proceeds to process your work. Oh and it would be good if Class_TaskFoo had another variable to hold the results.

Imports System.Threading.Tasks


Public Class Class1 '-- your main class

    Private colTask As New List(Of Task) '-- factory to add to.
    Dim tasktest As Class_TaskFoo '-- your small other class that will do your processing on the other thread.

    Sub main()

        tasktest = New Class_TaskFoo("somethimg") '-- send in your starting value you need to work ong
        colTask.Add(Task.Factory.StartNew(AddressOf tasktest.Start)) '-- add and start your task.

        For i As Int32 = 0 To 200 '-- just to highlight the processing here continues while the task is running
            Console.WriteLine(i)
        Next
    End Sub

End Class

Public Class Class_TaskFoo '-- small secondardy class to hold your processing.
    Private _sValue As String = String.Empty '-- your value you wanted to process

    Public Sub New(ByVal inputValue As String) '-- standard constructor
        _sValue = inputValue
    End Sub

    Public Sub Start() '-- your code that you want to operate
        '-- do you search here
        For i As Int32 = 1000 To 1200
            Console.WriteLine(i)
        Next

        'While Not colTask(0).IsCompleted
        '    '-- wait for the thread to be done
        '    Console.WriteLine("waiting")
        'End While
    End Sub
End Class


Oh for the love of god... I thought this was a VB.NET question. Do you need me to rewrite this in C#?
Was This Post Helpful? 0
  • +
  • -

#9 Curtis Rutland   User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 08:13 AM

You have to shift your logic for asynchronous processes. With a BackgroundWorker you'd be dealing with a callback, but with a task you don't necessarily have to. But you still can't just continue processing after the task as if it were synchronous. The code immediately after your task will be executed immediately, not when the task is done.

So, I propose you split your logic up into three methods. One to initiate the task, one to do the work, and one to update the UI. Here's a simple example:

private void button1_Click(object sender, EventArgs e) {
    Task.Factory.StartNew(() => DoSomeWork(2000));
}

private void DoSomeWork(int i) {
    Thread.Sleep(i);
    UpdateText("Updated!");
}

private void UpdateText(string p) {
    if (InvokeRequired)
        Invoke(new Action(() => UpdateText(p)));
    else
        button1.Text = p;
}


Now, the third method there handles updating the UI, since you can't do it from a cross-thread. It checks to see if the call is crossthread, and if it is, it invokes the call on the gui thread. Otherwise it just processes as normal.
Was This Post Helpful? 0
  • +
  • -

#10 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 09:57 AM

Just got back from a clients office. Thanks for your suggestions guys! :) This seems to be a great shift from traditional applications that's don't require tasks. New to me! :lol:

@Curtis: Can you explain a little what InvokeRequired is? Where is the UpdateText method getting that InvokeRequired value from? If it's true, it runs the UpdateText method again?

Can you give me a small play by play? I'll definitely work on this tonight, seems something very important I should know. :) Thanks!
Was This Post Helpful? 0
  • +
  • -

#11 Curtis Rutland   User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 10:37 AM

InvokeRequired is a property of the form (and every other control as well).

Quote

Gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on.


It does call UpdateText again, but not the way you think. It causes the thread the control exists on to call the method. In this case, the control is the form, so the GUI thread invokes the delegate you pass it. In my example, the delegate is a simple Action that calls the same method.

So, play-by-play:

You click a button. The event fires. In the event handler, we spin up a new task, in which we call a "worker method."

This worker method is started in a new thread. In this example, to simulate "loading", I sleep the thread for 2 seconds. Then, once the "loading" is done, I call a method to update text. In your case, you'd probably call a method to bind your results to the form or something.

So, now the UpdateText method is being called. First, we check if we're making a cross-thread call. Yes, we are. So instead of proceeding (and getting an exception) we create a delegate and tell the form to invoke it. The delegate is a lambda that re-makes the same call, with the same parameter. This time, it's coming from the GUI thread, so there's no need to invoke, so it can get on with what it's supposed to do.
Was This Post Helpful? 1
  • +
  • -

#12 [email protected]   User is offline

  • D.I.C Addict
  • member icon

Reputation: 1003
  • View blog
  • Posts: 975
  • Joined: 30-September 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 12:22 PM

You can also use continuations. The idea being that you do work to get the result in the first task (the antecedent), and then the second task (the continuation) uses that result to perform an action (an action that may include updating the GUI).


The following example has two tasks. The first tasks does work that will produce a result (and random integer in this example). Once that task has completed, the second task (returned from ContinueWith() is scheduled for execution. When executed, the first task is passed to this second task, and you get the result and update the GUI.

To resolve potential cross thread communication problems when updating the GUI, you simply get a TaskScheduler object for the current context by calling the static FromCurrentSynchronizationContext() method, and pass that as a second arguement to ContinueWith():

  public partial class Form1 : Form
    {
        private TaskScheduler scheduler = null;

        public Form1()
        {
            InitializeComponent();
            this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
        }

        private void BtnDoWork_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew<int>(() => doWork(3000)).ContinueWith(t => updateTextBox(t.Result.ToString()), this.scheduler);
        }

        private int doWork(int i)
        {
            Thread.Sleep(i);
            return new Random().Next(100);
        }

        private void updateTextBox(string text)
        {
            this.TxtResult.Text = text;
        }
    }



The call to t.Result is a blocking call (it won't return until the associated task that is to produce the result is complete), hence why your code still caused the GUI to hang :)

If you think about it, this is perfectly reasonable as the task obviously has to first complete and produce the result before a result is available.

This post has been edited by [email protected]: 12 May 2011 - 01:31 PM

Was This Post Helpful? 3
  • +
  • -

#13 Curtis Rutland   User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 5106
  • View blog
  • Posts: 9,283
  • Joined: 08-June 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 12:25 PM

Excellent advice. I won't pretend that I'm fully familiar with Tasks, as I'm much more used to BackgroundWorker myself.

Actually, @[email protected], do you think you might have time soon to write a Tasks tutorial for the Learning C# Series? It would be a big help.

This post has been edited by Curtis Rutland: 12 May 2011 - 12:32 PM

Was This Post Helpful? 0
  • +
  • -

#14 eclipsed4utoo   User is offline

  • Not Your Ordinary Programmer
  • member icon

Reputation: 1536
  • View blog
  • Posts: 5,972
  • Joined: 21-March 08

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 02:24 PM

We already have a tutorial on Tasks. It was done by [email protected].(not sure why he didn't link it). I've used this tutorial a number of times.
Was This Post Helpful? 1
  • +
  • -

#15 Sergio Tapia   User is offline

  • D.I.C Lover
  • member icon

Reputation: 1258
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Re: How can I use a BackgroundWorker in my WinForms application?

Posted 12 May 2011 - 04:11 PM

Thanks so much for all the feedback guys! That tutorial written by CodingSupernatural is just fantastic! A++++ would read again. :lol:

Let me get some coffee, some cookies, put the kids to bed, and then it's me, Visual Studio 2010 and the debugger.

I hope you guys like the little app I'm going to release soon. It's something I hope normal people (non programmers hehe) will like using. :D
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2