6

I need some help trying to figure what I'm doing wrong. I'm trying to get a collection of items from the system log on a separate thread to keep the form from being frozen during the collection process. I can get the background worker to grab them all, but I am having some issues add them to the ListBox on the form.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{

  foreach (System.Diagnostics.EventLogEntry entry in eventLog1.Entries)
  {
     listBox1.Items.Add(
        entry.EntryType.ToString() + " - " + 
        entry.TimeWritten + "     - " + 
        entry.Source);
  }
}

Obviously this doesn't work as expected, since there are 2 separate threads, and you can't change objects on different threads, as I have found out. So, If someone could guide me in the right direction, I would be thankful.

Alex Aza
  • 76,499
  • 26
  • 155
  • 134
Rekar
  • 495
  • 2
  • 8
  • 19
  • 1
    Take a look on this answer: http://stackoverflow.com/questions/1136399/how-to-update-textbox-on-gui-from-another-thread-in-c# – Klinger Jun 12 '11 at 07:25

5 Answers5

4

You should not access UI elements from non-UI thread. Run ReportProgress, which will be synced with UI thread.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    foreach (System.Diagnostics.EventLogEntry entry in eventLog1.Entries)
    {
        var newEntry = entry.EntryType + " - " + entry.TimeWritten + "     - " + entry.Source;
        backgroundWorker1.ReportProgress(0, newEntry);
    }
}

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var newEntry = (string)e.UserState;
    listBox1.Items.Add(newEntry);
}

Make sure you enable WorkerReportsProgress.

backgroundWorker1.WorkerReportsProgress = true;

and subscribed to ProgressChanged

backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;

Another approach is to call Control.Invoke inside

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    foreach (System.Diagnostics.EventLogEntry entry in eventLog1.Entries)
    {
        var newEntry = entry.EntryType.ToString() + " - " + entry.TimeWritten + "     - " + entry.Source;
        Action action = () => listBox1.Items.Add(newEntry);
        Invoke(action);
    }
}

But with this approach you don't need BackgroundWorker as whole point of it is to use ProgressChanged and RunWorkerCompleted event handler which are synced with the UI thread.

Alex Aza
  • 76,499
  • 26
  • 155
  • 134
1

You need to use the report progress option of the background worker. google this

Chen Kinnrot
  • 20,609
  • 17
  • 79
  • 141
1

There should not be any issues as you are using BackgroundWorker. All the call to the callback method runs on the same UI context.

EDIT:

if you want to report progress, you need to store SynchronizationContext.Current to preferably startup. or you can Use IsInvokeRequired pattern. Here is how I use SynchronizationContext

 private SynchronizationContext uiContext;
        public Form1()
        {
            uiContext = SynchronizationContext.Current;
            InitializeComponent();
            FillItem();
        }

I have following code , and it is working like charm.

    public void FillItem()
            {
                BackgroundWorker worker = new BackgroundWorker();
                worker.WorkerReportsProgress = true;
                worker.DoWork += (a, b) =>
                                     {
                                         int i = 0; //Percentage complete, roll your own logic.
                                         foreach (var eventLog in EventLog.GetEventLogs())
                                         {
                                             foreach (EventLogEntry entry in eventLog.Entries)
                                             {
                                                 this.listBox1.Items.Add(entry.Message);
 uiContext.Post(z=>worker.ReportProgress(i++),null);

                                             }
                                         }


                                     };
                worker.RunWorkerAsync();
                worker.ProgressChanged += (a, b) => this.progressBar1.Value = b.ProgressPercentage;


            }
crypted
  • 10,118
  • 3
  • 39
  • 52
1

Try this, very simple way of invoking an action on the Control's thread:

private void Form1_Load(object sender, EventArgs e)
{
    var bw = new BackgroundWorker();
    bw.DoWork += DoWork;
    bw.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
    var itemList = new List<int> {1, 22, 3, 4};
    var func = new Action<int>(itemToAdd => listBox1.Items.Add(itemToAdd));
    foreach (var item in itemList)
    {
        listBox1.Invoke(func, item);
    }
}
Rob
  • 26,989
  • 16
  • 82
  • 98
0

Only the GUI thread is allowed to modify GUI elements. If you do not respect this rule, you will get an exception. You may:

  1. use a MethodInvoker to trigger a call from within the GUI thread
  2. use the report progress feature (which does actually 1. For you)
  3. store the objects in another data structure (lock) and use a timer in the GUI thread to pull the objects and display them.
jdehaan
  • 19,700
  • 6
  • 57
  • 97