chasher
chasher

Reputation: 149

keep windows.forms controls in same Main thread

So, I have a win form that calls a method

dvm.SetVoltage(excitationVoltage, rigNo);

which runs a task in another class

public void SetVoltage(double voltage, int rigNum)
{
    Task.Run(() => ReadDVMWorker());
}

Once the worker is finished (voltage set) it triggers an event In the main Form1.cs

private void dvmVoltageSet(object sender, VoltageEventArgs e)
{
    VoltageSet = e.VolSet;
    TestLvdtNull();
}

Calling TestLvdtNull method:

private void TestLvdtNull()
{
     tbMessage.Location = new Point((int)(x / 2 - 250), 150);
}

As soon as the tbMessage line is reached it causes an exception because it has started another thread other than the one tbMessage was created in, how can I prevent it from starting a new thread and continue using the Main thread please?

I have looked at singlethreadsynchronizationcontext, but couldn't make it compile and I know that you can invoke:

tbMessage.Invoke((Action)delegate
{
    tbMessage.Location = new Point((int)(x / 2 - 250), 150);
});

But I have many controls with many attributes changing, there must be a way to keep the UI on the main thread?

Upvotes: 0

Views: 191

Answers (3)

Jan Zahradník
Jan Zahradník

Reputation: 2497

All UI controls are created at one thread. That is by design in many UI frameworks. After you finish your task you have to return to the UI thread to access UI controls.

One option mentioned in comments is to use async/await where the part of the method after await keyword is executed on the same thread as was before the async method.

// UI thread
await ReadDVMWorker(); // executed at ThreadPool
// UI thread

If you prefer to stay with Task, you can use ContinueWith method with correct TaskScheduler parameter, which ensures that you're back to UI thread. Eg. TaskScheduler.FromCurrentSynchronizationContext()

Async/await attempt code:

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Display the result.
    textBox1.Text += result;
}

//The following method runs asynchronously.The UI thread is not
//blocked during the delay.You can move or resize the Form1 window 
//while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await dvm.SetVoltage(5, rigNo); //Task.Delay(10000);
    return "Finished";
}

Upvotes: 1

chasher
chasher

Reputation: 149

After trying several different ways to solve the problem, I solved the problem by using SynchronizationContext.

This grabs the SyncronizationContext of the thread:

private SynchronizationContext _synchronizationContext;
SynchronizationContext uiContext = SynchronizationContext.Current;

Then after running my task in another class, where previously I was getting an cross thread call exception, I call the method that wants use the same UI thread:

uiContext.Post(MethodToCallOnTheSameUIThread, "string");

After this I can modify and update my textboxes and other controls!

You can check the thread id by:

int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Thread: " + id);

With thanks to Mike Peretz and his CodeProject

Upvotes: 0

oo_dev
oo_dev

Reputation: 777

You could have a method to update arbitrary controls

    private void dvmVoltageSet(object sender, VoltageEventArgs e)
    {
        VoltageSet = e.VolSet;
        TestLvdtNull(tbMessage);
        TestLvdtNull(tbMessage2);
    }

    private void TestLvdtNull(Control control)
    {
        control.BeginInvoke((MethodInvoker)delegate()
            {
                control.Location += new Point((int)(x / 2 - 250), 150);
            });
    }

Upvotes: 0

Related Questions