そうです、コントロールをスレッドに渡すのは良くありません。Winforms コントロールはシングルスレッドであり、それらを複数のスレッドに渡すと、競合状態が発生したり、UI が壊れたりする可能性があります。代わりに、スレッドの機能を UI で使用できるようにし、UI の準備が整ったときにスレッドが呼び出されるようにする必要があります。バックグラウンド スレッドで UI の変更をトリガーする場合は、バックグラウンド イベントを公開し、UI からサブスクライブします。スレッドはいつでもイベントを発生させることができ、UI は可能なときにイベントに応答できます。
UI スレッドをブロックしないスレッド間の双方向通信を作成するには、多くの作業が必要です。以下は、BackgroundWorker クラスを使用した非常に簡略化された例です。
public class MyBackgroundThread : BackgroundWorker
{
    public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething;
    public MyStatus TheUIWantsToKnowThis { get { whatever... } }
    public void TheUIWantsMeToDoSomething()
    {
        // Do something...
    }
    protected override void OnDoWork(DoWorkEventArgs e)
    {
        // This is called when the thread is started
        while (!CancellationPending)
        {
            // The UI will set IWantTheUIToDoSomething when it is ready to do things.
            if ((IWantTheUIToDoSomething != null) && IHaveUIData())
                IWantTheUIToDoSomething( this, new ClassToPassToUI(uiData) );
        }
    }
}
public partial class MyUIClass : Form
{
    MyBackgroundThread backgroundThread;
    delegate void ChangeUICallback(object sender, ClassToPassToUI uiData);
    ...
    public MyUIClass
    {
        backgroundThread = new MyBackgroundThread();
        // Do this when you're ready for requests from background threads:
        backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI);
        // This will run MyBackgroundThread.OnDoWork in a background thread:
        backgroundThread.RunWorkerAsync();
    }
    private void UserClickedAButtonOrSomething(object sender, EventArgs e)
    {
        // Really this should be done in the background thread,
        // it is here as an example of calling a background task from the UI.
        if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests)
            backgroundThread.TheUIWantsMeToDoSomething();
        // The UI can change the UI as well, this will not need marshalling.
        SomeoneWantsToChangeTheUI( this, new ClassToPassToUI(localData) );
    }
    void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData)
    {
        if (InvokeRequired)
        {
            // A background thread wants to change the UI.
            if (iAmInAStateWhereTheUICanBeChanged)
            {
                var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI);
                Invoke(callback, new object[] { sender, uiData });
            }
        }
        else
        {
            // This is on the UI thread, either because it was called from the UI or was marshalled.
            ChangeTheUI(uiData)
        }
    }
}