Unity Editor的线程安全文件系统观察器

时间:2016-10-19 19:59:12

标签: c# multithreading unity3d mono filesystemwatcher

我需要一个文件系统观察器的线程安全类才能在Unity编辑器中使用,我已经知道在协同程序中不能使用线程,但我不知道编辑器中也不允许线程化

所以,有我的错误:

  

get_isEditor只能从主线程调用。建设者和   字段初始化程序将在加载线程时执行   加载场景。不要在构造函数或字段中使用此函数   初始化器,而是将初始化代码移动到Awake或Start   功能。 0x0000000140E431ED(Unity)StackWalker :: GetCurrentCallstack   0x0000000140E44EE1(Unity)StackWalker :: ShowCallstack   0x00000001405FC603(Unity)GetStacktrace 0x00000001405F97FE(Unity)   DebugStringToFile 0x00000001405F9C5C(Unity)DebugStringToFile   0x000000014035F7B3(Unity)ThreadAndSerializationSafeCheckReportError   0x0000000140E7B988(Unity)Application_Get_Custom_PropIsEditor   0x0000000015AC46AA(Mono JIT代码)(托管管理到本机)   UnityEngine.Application:get_isEditor()0x0000000015AC42FE(Mono JIT   代码)[Helpers.cs:585] Lerp2API.DebugHandler.Debug:Log(object)   0x0000000015AC41C2(Mono JIT代码)[Helpers.cs:578]   Lerp2API.DebugHandler.Debug:Log(string)0x0000000015AC40F7(Mono JIT)   代码)[LerpedEditorCore.cs:101]   Lerp2APIEditor.LerpedEditorCore:重新编译   (object,System.IO.FileSystemEventArgs)0x0000000015AC3F2D(Mono JIT)   代码)(包装器运行时调用)   :runtime_invoke_void__this ___ object_object   (object,intptr,intptr,intptr)0x00007FFB400A519B(mono)[mini.c:4937]   mono_jit_runtime_invoke 0x00007FFB3FFF84FD(mono)[object.c:2623]   mono_runtime_invoke 0x00007FFB3FFFE8F7(mono)[object.c:3827]   mono_runtime_invoke_array 0x00007FFB3FFFEBCC(mono)[object.c:5457]   mono_message_invoke 0x00007FFB4001EB8B(mono)[threadpool.c:1019]   mono_async_invoke 0x00007FFB4001F5E2(mono)[threadpool.c:1455]   async_invoke_thread 0x00007FFB4002329F(mono)[threads.c:685]   start_wrapper 0x00007FFB400D78C9(mono)[win32_threads.c:599]   thread_start 0x00007FFB77FC8364(KERNEL32)BaseThreadInitThunk

我复制了完整的堆栈跟踪,以便知道任何可能出现问题的帮助程序。因为,我搜索了一个解决方案,就像任何线程安全的FWS一样,是的,有一个,但仅适用于.NET 4,我需要一个用于.NET 2

这是我的代码:

using System.IO; //class, namespace, redundant info...

private static FileSystemWatcher m_Watcher;

[InitializeOnLoadMethod]
static void HookWatcher() 
{
    m_Watcher = new FileSystemWatcher("path", "*.cs");
    m_Watcher.NotifyFilter = NotifyFilters.LastWrite;
    m_Watcher.IncludeSubdirectories = true;
    //m_Watcher.Created += new FileSystemEventHandler(); //Add to the solution before compile
    //m_Watcher.Renamed += new FileSystemEventHandler(); //Rename to the solution before compile
    //m_Watcher.Deleted += new FileSystemEventHandler(); //Remove to the solution before compile
    m_Watcher.Changed += Recompile;
    m_Watcher.EnableRaisingEvents = true;
}

private static void Recompile(object sender, FileSystemEventArgs e) 
{
    Debug.Log("Origin files has been changed!");
}

没有什么特别的,你可以看到...

我看到的FSW是:https://gist.githubusercontent.com/bradsjm/2c839912294d0e2c008a/raw/c4a5c3d920ab46fdaa53b0e111e0d1204b1fe903/FileSystemWatcher.cs

我的目的很简单,我有一个独立的DLL来自我当前的Unity项目,这个想法很简单,我想从DLL的项目发生任何变化后自动重新编译所有内容,但我可以&#因为线程实现了这一点,所以我该怎么办?有没有其他方法可以监听与Unity兼容的文件?

感谢。

2 个答案:

答案 0 :(得分:2)

根据我的经验,您可以使用线程,但您必须注意只能从主线程执行对Unity类的访问。我建议每当看门狗发出警报时,将控制权移交给主线程。

static bool _triggerRecompile = false;

[InitializeOnLoadMethod]
static void HookWatcher() 
{
    m_Watcher = new FileSystemWatcher("path", "*.cs");
    // ....
    m_Watcher.Changed += Recompile;
    EditorApplication.update += OnEditorApplicationUpdate;
}

private static void Recompile(object sender, FileSystemEventArgs e) 
{
    bool _triggerRecompile = true;
    // Never call any Unity classes as we are not in the main thread
}

static void OnEditorApplicationUpdate ()
{
    // note that this is called very often (100/sec)
    if (_triggerRecompile)
    {
        _triggerRecompile = false;
        Debug.Log("Origin files has been changed!");
        DoRecompile();
    }
}
轮询当然有点讨厌和丑陋。一般来说,我更喜欢基于事件的方法但在这种特殊情况下,我认为没有机会欺骗主线程规则。

答案 1 :(得分:1)

我在@Kay的帮助下解决了它,谢谢@Kay!

我想做一个更通用的答案,所以我决定创建自己的课程以达到我想要的目标。这就是结果:

using System;
using System.IO;
using System.Collections.Generic;

namespace Lerp2APIEditor.Utility
{
    public class LerpedThread<T>
    {
        public T value = default(T);
        public bool isCalled = false;
        public string methodCalled = "";
        public Dictionary<string, Action> matchedMethods = new Dictionary<string, Action>();

        public FileSystemWatcher FSW
        {
            get
            {
                return (FileSystemWatcher)(object)value;
            }
        }
        public LerpedThread(string name, FSWParams pars)
        {
            if(typeof(T) == typeof(FileSystemWatcher))
            {
                FileSystemWatcher watcher = new FileSystemWatcher(pars.path, pars.filter);

                watcher.NotifyFilter = pars.notifiers;
                watcher.IncludeSubdirectories = pars.includeSubfolders;

                watcher.Changed += new FileSystemEventHandler(OnChanged);
                watcher.Created += new FileSystemEventHandler(OnCreated);
                watcher.Deleted += new FileSystemEventHandler(OnDeleted);
                watcher.Renamed += new RenamedEventHandler(OnRenamed);

                ApplyChanges(watcher);
            }
        }
        private void OnChanged(object source, FileSystemEventArgs e)
        {
            methodCalled = "OnChanged";
            isCalled = true;
        }
        private void OnCreated(object source, FileSystemEventArgs e)
        {
            methodCalled = "OnCreated";
            isCalled = true;
        }
        private void OnDeleted(object source, FileSystemEventArgs e)
        {
            methodCalled = "OnDeleted";
            isCalled = true;
        }
        private void OnRenamed(object source, RenamedEventArgs e)
        {
            methodCalled = "OnRenamed";
            isCalled = true;
        }
        public void StartFSW()
        {
            FSW.EnableRaisingEvents = true;
        }
        public void CancelFSW()
        {
            FSW.EnableRaisingEvents = false;
        }
        public void ApplyChanges<T1>(T1 obj)
        {
            value = (T)(object)obj;
        }
    }
    public class FSWParams
    {
        public string path,
                      filter;
        public NotifyFilters notifiers;
        public bool includeSubfolders;
        public FSWParams(string p, string f, NotifyFilters nf, bool isf)
        {
            path = p;
            filter = f;
            notifiers = nf;
            includeSubfolders = isf;
        }
    }
}

主类代码:

namespace Lerp2APIEditor
{
    public class LerpedEditorCore
    {

        private static LerpedThread<FileSystemWatcher> m_Watcher;

        [InitializeOnLoadMethod]
        static void HookWatchers() 
        {
            EditorApplication.update += OnEditorApplicationUpdate;

            m_Watcher.matchedMethods.Add("OnChanged", () => {
                Debug.Log("Origin files has been changed!");
            });

            m_Watcher.StartFSW();
        }

        static void OnEditorApplicationUpdate()
        {
            if(EditorApplication.timeSinceStartup > nextSeek)
            {
                if (m_Watcher.isCalled)
                {
                    foreach (KeyValuePair<string, Action> kv in m_Watcher.matchedMethods)
                        if (m_Watcher.methodCalled == kv.Key)
                            kv.Value();
                    m_Watcher.isCalled = false;
                }
                nextSeek = EditorApplication.timeSinceStartup + threadSeek;
            }
        }
    }
}

我所做的事情非常简单。我只创建了一个创建FSW实例的通用类或者你想听的任何东西。有一次创建,我附加的事件只激活了@Kay建议我使用的bool,以及调用的方法确切地知道调用了什么方法。

稍后在主类中,如果检测到更改,foreach会每秒循环列出的每个方法,并且会调用链接到字符串的方法。