0

I have a Windows Form application and managed DLL in one solution. DLL contains some time consuming functions during which I wish to update the Form contents (callback from the DLL to the Form with progess updates). I have the following code:

Form code, where I initialize the DLL and give it a callback function in the Initialize method. I also start a separate Thread to periodicly check the message_queue for new messages from the DLL. The DLL function is also called in a separate Thread (non blocking for the UI).

private LibraryDLL library_dll;
private ConcurrentQueue<string> message_queue;

public MainForm()
{
    InitializeComponent();
    library_dll = new LibraryDLL();
    message_queue = new ConcurrentQueue<string>();
    library_dll.Initialize(ProcessMessage);

    new Thread(() =>
    {
        Thread.CurrentThread.IsBackground = true;
        string message;
        if (message_queue.TryDequeue(out message))
        {
            PrintMessage(message);
        }
    }).Start();
}

private void ProcessMessage(string message)
{
    message_queue.Enqueue(message);
}

private void PrintMessage(string message)
{
    this.Invoke((MethodInvoker)delegate
    {
        listBox_rows.Items.Add(message);
    });            
}

private void button_send_Click(object sender, EventArgs e)
{
    new Thread(() =>
    {
        Thread.CurrentThread.IsBackground = true; 
        library_dll.DoWork();           
    }).Start();
}

In DLL code, I use the callback method to report progress:

private CallBack callback;
public delegate void CallBack(string message);

public LibraryDLL() { }

public void Initialize(CallBack callback)
{
    this.callback = callback;
}

public void DoWork()
{
    callback("working...")
    Thread.Sleep(500);
    callback("working...")
    Thread.Sleep(500);
    callback("working...")
    Thread.Sleep(500);
}

My problem is, that instead of string "working" appearing every 500ms, it appears 3 times after 1500ms (only after the Thread in which the DoWork method is running ends). I also tried the Invalidate()-Update()-Refresh() sequence in the Form's PrintMessage function, but without any effect.

Thanks for the advice!

EDIT1:

I modified the code to use the BackgroundWorker, however, the problem remains (nothing for 1500ms, than all 3 strings at once).

BackgroundWorker bck_worker;

public MainForm()
{
    InitializeComponent();
    library_dll = new LibraryDLL();
    library_dll.Initialize(bck_worker);

    bck_worker = new BackgroundWorker();
    bck_worker.ProgressChanged += new ProgressChangedEventHandler(bckWorker_ProgressChanged);
    bck_worker.WorkerReportsProgress = true;
    bck_worker.WorkerSupportsCancellation = true;
} 

private void bckWorker_DoWork(object sender, DoWorkEventArgs e)
{
    library_dll.DoWork();
}

private void bckWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    PrintMessage((string)e.UserState);
}

private void button_send_Click(object sender, EventArgs e)
{
    bck_worker.DoWork += new DoWorkEventHandler(bckWorker_DoWork);
    bck_worker.RunWorkerAsync();
}

private void PrintMessage(string message)
{
    listBox_rows.Items.Add(message);
}

And the DLL:

private BackgroundWorker bck_worker;

public LibraryDLL() { }

public void Initialize(BackgroundWorker bck_worker)
{
    this.bck_worker = bck_worker;
}

public void DoWork()
{
    bck_worker.ReportProgress(25, "working...");        
    Thread.Sleep(500);
    bck_worker.ReportProgress(50, "working...");
    Thread.Sleep(500);
    bck_worker.ReportProgress(75, "working...");
    Thread.Sleep(500);
}

EDIT2:

OK, I now tried to add the Invalidate-Update-Refresh sequence at the end of the PrintMessage function and it finaly works (with the BackgroundWorker approach)!

  • You can probably replace all this code with an `ActionBlock` [as shown here](https://stackoverflow.com/questions/32089932/updating-ui-control-from-actionblock). You don't need to use raw threads in .NET, you can use tasks, `async/await` and `Progress< T>` to process data asynchronously, update the UI from other threads etc – Panagiotis Kanavos Sep 10 '18 at 12:12
  • Even if you don't want to use tasks (why?) you can use a timer instead of blocking a thread to poll the ConcurrentQueue – Panagiotis Kanavos Sep 10 '18 at 12:26

1 Answers1

2

Use background worker and workers's report progress to update your UI: background worker doc

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
vhr
  • 1,528
  • 1
  • 13
  • 21
  • BGW is obsolete. Anything it does can be done easily with a simple `await Task.Run(...)` – Panagiotis Kanavos Sep 10 '18 at 12:08
  • 3
    when reporting progress it's a matter of personal preference in my opinion. try to read through this thread and decide for yourself: https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker – vhr Sep 10 '18 at 12:18
  • This is definitely not a matter of preference. The very link you provided says `async/await is designed to replace constructs such as the BackgroundWorker`. It's not just progress, and the fact that `Progress` can be used by *any* thread. BGWs can't be combined. Combining tasks though is almost trivial, you just use `await` between calls. BGWs don't understand time or input processing either. This question asks how to process *messages* asynchronously. – Panagiotis Kanavos Sep 10 '18 at 12:23
  • BGWs don't understand time either. The OP's code shows polling of a queue. This can be done easily using a System.Threading.Timer. A BGW would need a loop with a Thread.Sleep or Task.Delay to do the same. – Panagiotis Kanavos Sep 10 '18 at 12:24
  • [Here's a blog by Stephen Cleary about why you should use `Task` instead of BackgroundWorker.](https://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-intro.html) – Matthew Watson Sep 10 '18 at 12:26
  • 2
    you need to read all the answers:) and here's another blog post: https://jeremybytes.blogspot.com/2012/05/backgroundworker-component-im-not-dead.html – vhr Sep 10 '18 at 12:45
  • I tried to implement the BackgroundWorker solution, but without any effect. Please, see edit. – Tomáš 'Guns Blazing' Frček Sep 10 '18 at 15:25
  • 1
    I do agree with @vhr prefer `BackgroundWorker` over `Task.Run`, too. Probably mostly because I'm using it since 10+ years, and it behaves just as I want it to behave in terms of keeping the UI responsive, as well as being cancelable easily. – Uwe Keim Sep 10 '18 at 19:12