C # выполнить код в другом потоке - PullRequest
2 голосов
/ 19 декабря 2011

У меня возникли проблемы с многопоточностью в моем приложении.У меня есть многопоточное клиент-серверное приложение.Я также использую C # MonoDevelop для Unity3d.Не уверен, имеет ли это значение для ответа.Я попытаюсь объяснить, где моя проблема:

Unity работает в одном потоке.Поэтому, если я хочу создать экземпляр объекта, который использует абстрактный класс ScriptableObject из единицы, то это должно быть сделано в главном потоке, в котором работает Unity.

Но мой сокет сервера порождает поток для каждого подключенного клиента, чтобы входящие данные могли обрабатываться асинхронно.Полученные данные обрабатываются с помощью метода OnDataReceived() (который выполняется в собственном потоке)

Проблема в том, что я не могу создать экземпляр объекта Player внутри OnDataReceived()нить.Потому что мой Player объект наследуется от ScriptableObject.Это означает, что этот объект должен быть создан в главном потоке Unity.

Но я понятия не имею, как это сделать ... Есть ли способ вернуться обратно в основной поток, так что я все еще могу создать Player объект в методе OnDataReceived()?

Ответы [ 2 ]

4 голосов
/ 19 декабря 2011

.NET уже имеет концепцию SynchronizationContext, наиболее часто используемую для приложений пользовательского интерфейса, где сходство потоков требуется для вызова операций с элементами управления пользовательского интерфейса (например, в WPF или WinForms).Однако даже вне приложения пользовательского интерфейса вы можете повторно использовать эти концепции для общей рабочей очереди с привязкой к потокам.

В этом примере показано, как использовать 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.
2 голосов
/ 19 декабря 2011

Вы можете общаться с исходным потоком через класс, такой как

class Communicator
{
    public static volatile bool CreatePlayer;
}

А в коде сокета измените переменную CreatePlayer. В коде получателя проверьте переменную и создайте плеер. После этого установите CreatePlayer в false. Точно так же и с другими вещами. Будьте осторожны при манипулировании одной переменной в двух потоках одновременно - например, может быть лучше иметь четыре логических значения для CreatePlayer, чем иметь int NumPlayersToCreate, чтобы оба потока не пытались постоянно обращаться к одним и тем же данным. Конечно, вам придется профилировать и посмотреть. И последнее: убедитесь, что переменные, измененные в обоих потоках, помечены как изменчивые. Это позволяет каждому потоку обращаться к данным из основной памяти, а не хранить их в кэше (в противном случае каждый поток не заметит, что данные были изменены в кэше другого потока).

Да, это не самое эффективное и элегантное решение, но оно самое простое. Я уверен, что кто-то предложит что-то более сложное; если хочешь, я тоже могу это сделать. Тем не менее, вы, кажется, не знакомы с многопоточностью, поэтому я подумал, что вы захотите начать с чего-то простого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...