Henrik P. Hessel
Henrik P. Hessel

Reputation: 36617

C# Func Delegate throws Thread Exception

So, maybe I misunderstood the usage of Func but

Func<ComboBox, string> getCurrentValue = s => s.SelectedValue.ToString();

Creates a Thread Error when calling getCurrentValue(cb_message_type) from my Workerthread. Whats the best solution to get the Selected Value of the Combobox?

Many thanks,
rAyt

Edit
MSDN

"The underlying type of a lambda expression is one of the generic Func delegates. This makes it possible to pass a lambda expression as a parameter without explicitly assigning it to a delegate."

Upvotes: 0

Views: 1483

Answers (5)

supercat
supercat

Reputation: 81159

If the thread will simply be reading the ComboBox, the best option if practical is probably to have an event handler on the thread which grabs the ComboBox value any time it changes, and then exposes that value via property which can be read from any thread.

Upvotes: 0

Anton Gogolev
Anton Gogolev

Reputation: 115741

Generally speaking, you cannot access UI controls from a thread other than that they were created on. To overcome that, you'll either have to check ISynchronizeInvoke.InvokeRequired on the control in question and branch, invoke a delegate, etc. or use a SynchronizationContext. First option is very cumbersome, whereas second one is pretty elegant:

var synchronizationContext = WindowsFormsSynchronizationContext.Current;
string currentValue = "";    

synchronizationContext.Send(
    () => currentValue = getCurrentValue(comboBox), 
    null);

Upvotes: 1

SLaks
SLaks

Reputation: 887413

The problem is that UI controls can only be used on the UI thread,

You need to call the Invoke method within the other thread, like this:

Func<ComboBox, string> getCurrentValue =
    s => s.Invoke(new Func<object>(() => s.SelectedValue)).ToString();

The Invoke method takes a delegate and executes it on the UI thread.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1500495

You need to call Control.Invoke with that delegate - or make the delegate itself call it. Using a lambda expression doesn't change the threading requirements of Windows Forms - it just makes it easier to create a delegate.

You might want to make a convenience method to do this:

// (Untested)
public static Func<TControl, TResult> WrapInvocation(Func<TControl,TResult> func)
    where TControl : Control
{
    return control => {
        return (TResult) control.Invoke(func);
    };
}

Use as:

Func<ComboBox, string> getCurrentValue = s => s.SelectedValue.ToString();
getCurrentValue = WrapInvocation(getCurrentValue);

Then you can call getCurrentValue(comboBox) from any thread.

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062770

Since windows controls have thread affinity, you have 2 options:

  1. query this data before doing your threading code, for example passing it in as the state to the worker
  2. query it in the worker via Control.Invoke

Since the first is trivial, I'll give an example of the second using captured variables:

object value = null;
yourCombo.Invoke((MethodInvoker) delegate {value=yourCombo.SelectedValue;});
string s = value.ToString();

Here the bits inside delegate {...} happen on the UI thread, even if the code around it is on the worker thread. You can mix the above either inside your func, or call the entire func once you've switched threads.

Upvotes: 5

Related Questions