C#在其他线程上执行代码

时间:2011-12-18 20:11:49

标签: c# wpf multithreading unity3d

我在应用程序中遇到线程问题。我有一个多线程客户端/服务器应用程序。我也在为Unity3d使用C#MonoDevelop。不确定答案是否有任何不同。我会尝试解释我的问题所在:

Unity适用于单个线程。因此,如果我想实例化一个使用统一的抽象类ScriptableObject的对象,那么必须在Unity运行的主线程上完成。

但我的服务器套接字会为每个连接的客户端生成一个线程,以便可以将传入的数据处理为异步。接收的数据在OnDataReceived()方法(在其自己的线程上运行)

中处理

这里的问题是,我无法在Player线程内创建OnDataReceived()对象的实例。因为我的Player对象继承自ScriptableObject。这意味着应该在主Unity线程上创建此对象。

但是我不知道怎么做...有没有办法切换回主线程,所以我仍然可以在Player方法中创建一个OnDataReceived()对象?

2 个答案:

答案 0 :(得分:4)

.NET已经有了SynchronizationContext的概念,最常用于UI应用程序,其中需要线程关联来调用UI控件上的操作(例如,在WPF或WinForms中)。但是,即使在UI应用程序之外,您也可以将这些概念重用于通用的线程关联工作队列。

此示例演示如何在简单的控制台应用程序中使用WPF DispatcherSynchronizationContext(来自WindowsBase.dll)以及.NET 4.0任务类(TaskScheduler / Task )在主程序线程上调用源自子线程的动作。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;

internal sealed class Program
{
    private static void Main(string[] args)
    {
        int threadCount = 2;
        using (ThreadData data = new ThreadData(threadCount))
        {
            Thread[] threads = new Thread[threadCount];
            for (int i = 0; i < threadCount; ++i)
            {
                threads[i] = new Thread(DoOperations);
            }

            foreach (Thread thread in threads)
            {
                thread.Start(data);
            }

            Console.WriteLine("Starting...");

            // Start and wait here while all work is dispatched.
            data.RunDispatcher();
        }

        // Dispatcher has exited.
        Console.WriteLine("Shutdown.");
    }

    private static void DoOperations(object objData)
    {
        ThreadData data = (ThreadData)objData;
        try
        {
            // Start scheduling operations from child thread.
            for (int i = 0; i < 5; ++i)
            {
                int t = Thread.CurrentThread.ManagedThreadId;
                int n = i;
                data.ExecuteTask(() => SayHello(t, n));
            }
        }
        finally
        {
            // Child thread is done.
            data.OnThreadCompleted();
        }
    }

    private static void SayHello(int requestingThreadId, int operationNumber)
    {
        Console.WriteLine(
            "Saying hello from thread {0} ({1}) on thread {2}.",
            requestingThreadId,
            operationNumber,
            Thread.CurrentThread.ManagedThreadId);
    }

    private sealed class ThreadData : IDisposable
    {
        private readonly Dispatcher dispatcher;
        private readonly TaskScheduler scheduler;
        private readonly TaskFactory factory;
        private readonly CountdownEvent countdownEvent;

        // In this example, we initialize the countdown event with the total number
        // of child threads so that we know when all threads are finished scheduling
        // work.
        public ThreadData(int threadCount)
        {
            this.dispatcher = Dispatcher.CurrentDispatcher;
            SynchronizationContext context = 
                new DispatcherSynchronizationContext(this.dispatcher);
            SynchronizationContext.SetSynchronizationContext(context);
            this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
            this.factory = new TaskFactory(this.scheduler);
            this.countdownEvent = new CountdownEvent(threadCount);
        }

        // This method should be called by a child thread when it wants to invoke
        // an operation back on the main dispatcher thread.  This will block until
        // the method is done executing.
        public void ExecuteTask(Action action)
        {
            Task task = this.factory.StartNew(action);
            task.Wait();
        }

        // This method should be called by threads when they are done
        // scheduling work.
        public void OnThreadCompleted()
        {
            bool allThreadsFinished = this.countdownEvent.Signal();
            if (allThreadsFinished)
            {
                this.dispatcher.InvokeShutdown();
            }
        }

        // This method should be called by the main thread so that it will begin
        // processing the work scheduled by child threads. It will return when
        // the dispatcher is shutdown.
        public void RunDispatcher()
        {
            Dispatcher.Run();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        // Dispose all IDisposable resources.
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.countdownEvent.Dispose();
            }
        }
    }
}

示例输出:

Starting...
Saying hello from thread 3 (0) on thread 1.
Saying hello from thread 4 (0) on thread 1.
Saying hello from thread 3 (1) on thread 1.
Saying hello from thread 4 (1) on thread 1.
Saying hello from thread 3 (2) on thread 1.
Saying hello from thread 4 (2) on thread 1.
Saying hello from thread 3 (3) on thread 1.
Saying hello from thread 4 (3) on thread 1.
Saying hello from thread 3 (4) on thread 1.
Saying hello from thread 4 (4) on thread 1.
Shutdown.

答案 1 :(得分:2)

您可以通过诸如

之类的类与原始线程进行通信
class Communicator
{
    public static volatile bool CreatePlayer;
}

在套接字代码中,更改CreatePlayer变量。在接收器代码中,检查变量并创建一个播放器。之后,将CreatePlayer设置为false。与其他事物相似。注意同时在两个线程上操作一个变量 - 例如,为CreatePlayer设置四个布线可能比使用int NumPlayersToCreate更好,这样两个线程都不会尝试不断访问相同的数据。当然,你必须剖析和看到。最后一件事:确保两个线程中更改的变量都标记为volatile。这使得每个线程都从主内存访问数据而不是将其保存在缓存中(否则,每个线程都不会注意到在另一个线程的缓存中更改的数据)。

是的,这不是最高效或最优雅的解决方案,但它是最简单的。我相信有人会建议更多的内容;如果你愿意,我也可以这样做。但是,你似乎不熟悉多线程,所以我认为你想要一些简单易用的东西。