基于 C# 的 Windows 服务 - 尝试在生产中进行 JIT 调试

发布于 2024-09-28 07:46:15 字数 21754 浏览 5 评论 0原文

我在投入生产的服务的事件日志中收到此错误:

发生未处理的 win32 异常 在 RivWorks.FeedHandler.exe [5496] 中。 即时调试此异常 失败并出现以下错误: 调试器无法启动,因为 没有用户登录。

我已安装它并在 Win NT 全局帐户下运行。我不知道为什么它试图进入调试模式。它是在发布模型下构建的。运行在4.0框架上。

当我在我的开发机器上运行时,通过 EXE 入口点而不是 WinSvc 入口点,一切都运行得很好 - 但是 - 我已经处于“调试”模式。

有什么想法要寻找什么吗?


2010-10-21 - 注意 - 更改了代码库。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;
using System.Collections;

namespace RivWorks.FeedHandler.Service
{
    public partial class FeedListener : ServiceBase
    {
        #region Declarations
        private List<string> _keys = new List<string>();
        private System.Threading.Timer _clock = null;
        private FileSystemWatcher _watcher;
        private BackgroundWorker _worker;
        private Queue<string> _queue = new Queue<string>();
        private bool _isDequeueing = false;
        #endregion

        #region Constructor
        public FeedListener()
        {
            InitializeComponent();
        }
        #endregion

        #region Start/Stop
        protected override void OnStart(string[] args)
        {
            try
            {
                WriteToEventLog("Enter Start", EventLogEntryType.Information);
                _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

                _worker = new BackgroundWorker();
                _worker.WorkerReportsProgress = true;
                _worker.WorkerSupportsCancellation = true;
                _worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
                _worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
                _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);

                _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
                _watcher.IncludeSubdirectories = true;
                _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
                _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.EnableRaisingEvents = true;

                // check every 15 minutes...
                _clock = new System.Threading.Timer(Tick, null, 0, 900000);
                WriteToEventLog("Exit Start", EventLogEntryType.Information);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
                this.Stop();
            }
        }
        protected override void OnStop()  
        {
            try
            {
                _watcher.Dispose();
                _watcher = null;
                _clock.Dispose();
                _clock = null;
                _worker.Dispose();
                _worker = null;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Event Handlers
        void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
        {
            try
            {
                WriteToEventLog("Enter fileCreatedOrChanged", EventLogEntryType.Information);

                if (!_queue.Contains(e.FullPath))
                    _queue.Enqueue(e.FullPath);
                if (!_isDequeueing)
                    DeQueue();
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Do work on another Thread
        void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                WriteToEventLog("Enter BackgroundWorkerDoWork", EventLogEntryType.Information);
                BackgroundWorker bw = sender as BackgroundWorker;

                WriteToEventLog("Create Handler", EventLogEntryType.Information);
                RivWorks.FeedHandler.Library.Handler handler = new RivWorks.FeedHandler.Library.Handler(Convert.ToBoolean(AppSettings.Default.InProduction), AppSettings.Default.ArchivePath);

                WriteToEventLog("Setup Handler", EventLogEntryType.Information);
                handler.Keys = _keys;
                handler.RootDirectory = AppSettings.Default.RootDirectory;
                handler.FtpPath = AppSettings.Default.FTPRootPath;
                handler.WorkPath = AppSettings.Default.WorkPath;
                handler.ArchivePath = AppSettings.Default.ArchivePath;
                handler.EmailHost = AppSettings.Default.EmailHost;
                handler.EmailPassword = AppSettings.Default.EmailPassword;
                handler.EmailUser = AppSettings.Default.EmailUser;
                handler.ErrorNotificationRecipients = AppSettings.Default.ErrorNotificationRecipients;
                handler.InProduction = Convert.ToBoolean(AppSettings.Default.InProduction);

                Library.DTO.FileHandler fileHandler = new Library.DTO.FileHandler(handler.FtpPath, handler.WorkPath, handler.ArchivePath, (string)e.Argument);

                WriteToEventLog("Call Handler.Execute", EventLogEntryType.Information);
                handler.Execute(bw, e, fileHandler);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                WriteToEventLog("Exit BackgroundWorkerDoWork", EventLogEntryType.Information);
            }
        }
        void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage >= 100)
                {
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (e.Cancelled)
                {
                    WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
                    if (e.Error != null)
                    {
                        WriteToEventLog(e.Error, EventLogEntryType.Error);
                    }
                }
                else if (e.Error != null)
                {
                    WriteToEventLog(e.Error, EventLogEntryType.Error);
                }
                else
                {
                    WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Private Methods
        private void Tick(object data)
        {
            try
            {
                if (!_isDequeueing)
                {
                    WriteToEventLog("Enter Tick.  FTP Root = " + AppSettings.Default.FTPRootPath, EventLogEntryType.Information);

                    foreach (string key in _keys)
                    {
                        List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
                        foreach (string fileName in files)
                        {
                            if (File.Exists(fileName))
                            {
                                // Toss this file name into the Queue...
                                WriteToEventLog("Call _queue.Enqueue(" + fileName + ")", EventLogEntryType.Information);
                                if (!_queue.Contains(fileName))
                                    _queue.Enqueue(fileName);
                            }
                        }
                    }
                    // Now, start handling the list of files...
                    DeQueue();
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                WriteToEventLog("Exit Tick", EventLogEntryType.Information);
            }
        }
        private void DeQueue()
        {
            try
            {
                _isDequeueing = true;

                WriteToEventLog("Enter DeQueue", EventLogEntryType.Information);

                while (_queue.Count > 0)
                {
                    string queuedFile = _queue.Dequeue();
                    WriteToEventLog("DeQueued " + queuedFile, EventLogEntryType.Information);

                    bool isValid = false;
                    foreach (string key in _keys)
                    {
                        if (Path.GetExtension(queuedFile).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
                            isValid = true;
                    }

                    if (isValid)
                    {
                        // Now, spin up a new thread and do the work on the file, based on file type...
                        WriteToEventLog("Call RunWorkerAsync", EventLogEntryType.Information);
                        string UserName = Path.GetDirectoryName(queuedFile).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
                        int i = 0;
                        DateTime sTime = DateTime.Now;
                        DateTime eTime = DateTime.Now;
                        _worker.RunWorkerAsync(queuedFile);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //

                        while(_worker.IsBusy)
                        {
                            System.Threading.Thread.Sleep(5000);
                            i++;
                        }
                        eTime = DateTime.Now;
                        TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
                        string msg = String.Format("Import for {0} started at {1} and ended at {2}.  It took {3} cycles and the elapsed time was {4}:{5}:{6}.", UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
                        WriteToEventLog(msg, EventLogEntryType.Information);
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                _isDequeueing = false;
                WriteToEventLog("Exit DeQueue", EventLogEntryType.Information);
            }
        }
        private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
        {
            try
            {
                string message = string.Empty;
                string sTrace = ex.StackTrace;
                while (ex != null)
                {
                    message = message + Environment.NewLine + Environment.NewLine + ex.Message;
                    ex = ex.InnerException;
                }
                message = message + Environment.NewLine + Environment.NewLine + sTrace;

                WriteToEventLog(message, eventLogEntryType);
            }
            catch (Exception ex2)
            {
                WriteToEventLog(ex2.Message, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
        {
            try
            {
                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion
    }
}

2010-10-20 - 注意 - 添加了服务代码文件。也许这里有一个基本错误?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;

namespace RivWorks.FeedHandler.Service
{
    public partial class FeedListener : ServiceBase
    {
        #region Declarations
        private List<string> _keys = new List<string>();
        private System.Threading.Timer _clock = null;
        private FileSystemWatcher _watcher;
        private BackgroundWorker _worker;
        static private bool _isBusy = false;
        #endregion

        #region Constructor
        public FeedListener()
        {
            InitializeComponent();
        }
        #endregion

        #region Start/Stop
        protected override void OnStart(string[] args)
        {
            try
            {
                _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

                _worker = new BackgroundWorker();
                _worker.WorkerReportsProgress = true;
                _worker.WorkerSupportsCancellation = true;
                _worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
                _worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
                _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);

                _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
                _watcher.IncludeSubdirectories = true;
                _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
                _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.EnableRaisingEvents = true;

                // check every 5 minutes...
                _clock = new System.Threading.Timer(Tick, null, 0, 300000);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
                this.Stop();
            }
        }
        protected override void OnStop()  
        {
            try
            {
                _watcher.Dispose();
                _watcher = null;
                _clock.Dispose();
                _clock = null;
                _worker.Dispose();
                _worker = null;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Event Handlers
        void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
        {
            try
            {
                DTO.BackgroundWorkerEventArgs eventArgs = new DTO.BackgroundWorkerEventArgs();
                sysIO.WatcherChangeTypes myType = e.ChangeType;

                bool isValid = false;
                foreach (string key in _keys)
                {
                    if (Path.GetExtension(e.FullPath).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
                        isValid = true;
                }

                if (isValid)
                {
                    eventArgs.PathAndFile = e.FullPath;
                    eventArgs.Key = Path.GetExtension(e.FullPath).ToLower().Replace(".", "");
                    eventArgs.FileName = Path.GetFileName(e.FullPath);
                    eventArgs.Path = Path.GetDirectoryName(e.FullPath);
                    eventArgs.UserName = Path.GetDirectoryName(e.FullPath).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
                    eventArgs.IsRunning = true;

                    System.Threading.Thread.Sleep(30000);

                    // Now, spin up a new thread and do the work on the file, based on file type...
                    _isBusy = true;
                    _worker.RunWorkerAsync(eventArgs);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
                    int i = 0;
                    DateTime sTime = DateTime.Now;
                    DateTime eTime = DateTime.Now;
                    while (_isBusy)
                    {
                        System.Threading.Thread.Sleep(5000);
                        i++;
                    }
                    eTime = DateTime.Now;
                    TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
                    string msg = String.Format("Import for {0} started at {1} and ended at {2}.  It took {3} cycles and the elapsed time was {4}:{5}:{6}.", eventArgs.UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
                    WriteToEventLog(msg, EventLogEntryType.Information);
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Do work on another Thread
        void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                RivWorks.FeedHandler.Handler handler = new RivWorks.FeedHandler.Handler();
                BackgroundWorker bw = sender as BackgroundWorker;

                handler.Execute(bw, e);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                _isBusy = false;
            }
        }
        void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage >= 100)
                {
                    _isBusy = false;
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (e.Cancelled)
                {
                    WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
                    if (e.Error != null)
                    {
                        WriteToEventLog(e.Error, EventLogEntryType.Error);
                    }
                }
                else if (e.Error != null)
                {
                    WriteToEventLog(e.Error, EventLogEntryType.Error);
                }
                else
                {
                    WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
                }
                _isBusy = false;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Private Methods
        private void Tick(object data)
        {
            try
            {
                foreach (string key in _keys)
                {
                    List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
                    foreach (string fileName in files)
                    {
                        System.Threading.Thread.Sleep(5000);
                        if (File.Exists(fileName))
                        {
                            DateTime lat = File.GetLastWriteTime(fileName);
                            try
                            {
                                File.SetLastWriteTime(fileName, DateTime.Now);
                            }
                            catch
                            {
                                // just catch and ignore with a short pause...
                                System.Threading.Thread.Sleep(5000);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
        {
            try
            {
                string message = string.Empty;
                string sTrace = ex.StackTrace;
                while (ex != null)
                {
                    message = message + Environment.NewLine + Environment.NewLine + ex.Message;
                    ex = ex.InnerException;
                }
                message = message + Environment.NewLine + Environment.NewLine + sTrace;

                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex2)
            {
                WriteToEventLog(ex2, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
        {
            try
            {
                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion
    }
}

I am getting this error in my event logs for a service I put into production:

An unhandled win32 exception occurred
in RivWorks.FeedHandler.exe [5496].
Just-In-Time debugging this exception
failed with the following error:
Debugger could not be started because
no user is logged on.

I have it installed and running under a Win NT global account. I have no idea why it is trying to drop into debugging mode. It was built under the Release model. Running on the 4.0 Framework.

When I run on my dev machine, via an EXE entry point instead of the WinSvc entry point, everything runs just fine - BUT - I am already in "debug" mode.

Any ideas of what to look for?


2010-10-21 - NOTE - Changed the code base.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;
using System.Collections;

namespace RivWorks.FeedHandler.Service
{
    public partial class FeedListener : ServiceBase
    {
        #region Declarations
        private List<string> _keys = new List<string>();
        private System.Threading.Timer _clock = null;
        private FileSystemWatcher _watcher;
        private BackgroundWorker _worker;
        private Queue<string> _queue = new Queue<string>();
        private bool _isDequeueing = false;
        #endregion

        #region Constructor
        public FeedListener()
        {
            InitializeComponent();
        }
        #endregion

        #region Start/Stop
        protected override void OnStart(string[] args)
        {
            try
            {
                WriteToEventLog("Enter Start", EventLogEntryType.Information);
                _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

                _worker = new BackgroundWorker();
                _worker.WorkerReportsProgress = true;
                _worker.WorkerSupportsCancellation = true;
                _worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
                _worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
                _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);

                _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
                _watcher.IncludeSubdirectories = true;
                _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
                _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.EnableRaisingEvents = true;

                // check every 15 minutes...
                _clock = new System.Threading.Timer(Tick, null, 0, 900000);
                WriteToEventLog("Exit Start", EventLogEntryType.Information);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
                this.Stop();
            }
        }
        protected override void OnStop()  
        {
            try
            {
                _watcher.Dispose();
                _watcher = null;
                _clock.Dispose();
                _clock = null;
                _worker.Dispose();
                _worker = null;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Event Handlers
        void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
        {
            try
            {
                WriteToEventLog("Enter fileCreatedOrChanged", EventLogEntryType.Information);

                if (!_queue.Contains(e.FullPath))
                    _queue.Enqueue(e.FullPath);
                if (!_isDequeueing)
                    DeQueue();
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Do work on another Thread
        void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                WriteToEventLog("Enter BackgroundWorkerDoWork", EventLogEntryType.Information);
                BackgroundWorker bw = sender as BackgroundWorker;

                WriteToEventLog("Create Handler", EventLogEntryType.Information);
                RivWorks.FeedHandler.Library.Handler handler = new RivWorks.FeedHandler.Library.Handler(Convert.ToBoolean(AppSettings.Default.InProduction), AppSettings.Default.ArchivePath);

                WriteToEventLog("Setup Handler", EventLogEntryType.Information);
                handler.Keys = _keys;
                handler.RootDirectory = AppSettings.Default.RootDirectory;
                handler.FtpPath = AppSettings.Default.FTPRootPath;
                handler.WorkPath = AppSettings.Default.WorkPath;
                handler.ArchivePath = AppSettings.Default.ArchivePath;
                handler.EmailHost = AppSettings.Default.EmailHost;
                handler.EmailPassword = AppSettings.Default.EmailPassword;
                handler.EmailUser = AppSettings.Default.EmailUser;
                handler.ErrorNotificationRecipients = AppSettings.Default.ErrorNotificationRecipients;
                handler.InProduction = Convert.ToBoolean(AppSettings.Default.InProduction);

                Library.DTO.FileHandler fileHandler = new Library.DTO.FileHandler(handler.FtpPath, handler.WorkPath, handler.ArchivePath, (string)e.Argument);

                WriteToEventLog("Call Handler.Execute", EventLogEntryType.Information);
                handler.Execute(bw, e, fileHandler);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                WriteToEventLog("Exit BackgroundWorkerDoWork", EventLogEntryType.Information);
            }
        }
        void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage >= 100)
                {
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (e.Cancelled)
                {
                    WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
                    if (e.Error != null)
                    {
                        WriteToEventLog(e.Error, EventLogEntryType.Error);
                    }
                }
                else if (e.Error != null)
                {
                    WriteToEventLog(e.Error, EventLogEntryType.Error);
                }
                else
                {
                    WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Private Methods
        private void Tick(object data)
        {
            try
            {
                if (!_isDequeueing)
                {
                    WriteToEventLog("Enter Tick.  FTP Root = " + AppSettings.Default.FTPRootPath, EventLogEntryType.Information);

                    foreach (string key in _keys)
                    {
                        List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
                        foreach (string fileName in files)
                        {
                            if (File.Exists(fileName))
                            {
                                // Toss this file name into the Queue...
                                WriteToEventLog("Call _queue.Enqueue(" + fileName + ")", EventLogEntryType.Information);
                                if (!_queue.Contains(fileName))
                                    _queue.Enqueue(fileName);
                            }
                        }
                    }
                    // Now, start handling the list of files...
                    DeQueue();
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                WriteToEventLog("Exit Tick", EventLogEntryType.Information);
            }
        }
        private void DeQueue()
        {
            try
            {
                _isDequeueing = true;

                WriteToEventLog("Enter DeQueue", EventLogEntryType.Information);

                while (_queue.Count > 0)
                {
                    string queuedFile = _queue.Dequeue();
                    WriteToEventLog("DeQueued " + queuedFile, EventLogEntryType.Information);

                    bool isValid = false;
                    foreach (string key in _keys)
                    {
                        if (Path.GetExtension(queuedFile).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
                            isValid = true;
                    }

                    if (isValid)
                    {
                        // Now, spin up a new thread and do the work on the file, based on file type...
                        WriteToEventLog("Call RunWorkerAsync", EventLogEntryType.Information);
                        string UserName = Path.GetDirectoryName(queuedFile).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
                        int i = 0;
                        DateTime sTime = DateTime.Now;
                        DateTime eTime = DateTime.Now;
                        _worker.RunWorkerAsync(queuedFile);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //

                        while(_worker.IsBusy)
                        {
                            System.Threading.Thread.Sleep(5000);
                            i++;
                        }
                        eTime = DateTime.Now;
                        TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
                        string msg = String.Format("Import for {0} started at {1} and ended at {2}.  It took {3} cycles and the elapsed time was {4}:{5}:{6}.", UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
                        WriteToEventLog(msg, EventLogEntryType.Information);
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                _isDequeueing = false;
                WriteToEventLog("Exit DeQueue", EventLogEntryType.Information);
            }
        }
        private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
        {
            try
            {
                string message = string.Empty;
                string sTrace = ex.StackTrace;
                while (ex != null)
                {
                    message = message + Environment.NewLine + Environment.NewLine + ex.Message;
                    ex = ex.InnerException;
                }
                message = message + Environment.NewLine + Environment.NewLine + sTrace;

                WriteToEventLog(message, eventLogEntryType);
            }
            catch (Exception ex2)
            {
                WriteToEventLog(ex2.Message, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
        {
            try
            {
                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion
    }
}

2010-10-20 - NOTE - Added the Service code file. Maybe there is an elementary mistake in here?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;

namespace RivWorks.FeedHandler.Service
{
    public partial class FeedListener : ServiceBase
    {
        #region Declarations
        private List<string> _keys = new List<string>();
        private System.Threading.Timer _clock = null;
        private FileSystemWatcher _watcher;
        private BackgroundWorker _worker;
        static private bool _isBusy = false;
        #endregion

        #region Constructor
        public FeedListener()
        {
            InitializeComponent();
        }
        #endregion

        #region Start/Stop
        protected override void OnStart(string[] args)
        {
            try
            {
                _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

                _worker = new BackgroundWorker();
                _worker.WorkerReportsProgress = true;
                _worker.WorkerSupportsCancellation = true;
                _worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
                _worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
                _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);

                _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
                _watcher.IncludeSubdirectories = true;
                _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
                _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
                _watcher.EnableRaisingEvents = true;

                // check every 5 minutes...
                _clock = new System.Threading.Timer(Tick, null, 0, 300000);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
                this.Stop();
            }
        }
        protected override void OnStop()  
        {
            try
            {
                _watcher.Dispose();
                _watcher = null;
                _clock.Dispose();
                _clock = null;
                _worker.Dispose();
                _worker = null;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Event Handlers
        void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
        {
            try
            {
                DTO.BackgroundWorkerEventArgs eventArgs = new DTO.BackgroundWorkerEventArgs();
                sysIO.WatcherChangeTypes myType = e.ChangeType;

                bool isValid = false;
                foreach (string key in _keys)
                {
                    if (Path.GetExtension(e.FullPath).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
                        isValid = true;
                }

                if (isValid)
                {
                    eventArgs.PathAndFile = e.FullPath;
                    eventArgs.Key = Path.GetExtension(e.FullPath).ToLower().Replace(".", "");
                    eventArgs.FileName = Path.GetFileName(e.FullPath);
                    eventArgs.Path = Path.GetDirectoryName(e.FullPath);
                    eventArgs.UserName = Path.GetDirectoryName(e.FullPath).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
                    eventArgs.IsRunning = true;

                    System.Threading.Thread.Sleep(30000);

                    // Now, spin up a new thread and do the work on the file, based on file type...
                    _isBusy = true;
                    _worker.RunWorkerAsync(eventArgs);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
                    int i = 0;
                    DateTime sTime = DateTime.Now;
                    DateTime eTime = DateTime.Now;
                    while (_isBusy)
                    {
                        System.Threading.Thread.Sleep(5000);
                        i++;
                    }
                    eTime = DateTime.Now;
                    TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
                    string msg = String.Format("Import for {0} started at {1} and ended at {2}.  It took {3} cycles and the elapsed time was {4}:{5}:{6}.", eventArgs.UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
                    WriteToEventLog(msg, EventLogEntryType.Information);
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Do work on another Thread
        void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                RivWorks.FeedHandler.Handler handler = new RivWorks.FeedHandler.Handler();
                BackgroundWorker bw = sender as BackgroundWorker;

                handler.Execute(bw, e);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
            finally
            {
                _isBusy = false;
            }
        }
        void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                if (e.ProgressPercentage >= 100)
                {
                    _isBusy = false;
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (e.Cancelled)
                {
                    WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
                    if (e.Error != null)
                    {
                        WriteToEventLog(e.Error, EventLogEntryType.Error);
                    }
                }
                else if (e.Error != null)
                {
                    WriteToEventLog(e.Error, EventLogEntryType.Error);
                }
                else
                {
                    WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
                }
                _isBusy = false;
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion

        #region Private Methods
        private void Tick(object data)
        {
            try
            {
                foreach (string key in _keys)
                {
                    List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
                    foreach (string fileName in files)
                    {
                        System.Threading.Thread.Sleep(5000);
                        if (File.Exists(fileName))
                        {
                            DateTime lat = File.GetLastWriteTime(fileName);
                            try
                            {
                                File.SetLastWriteTime(fileName, DateTime.Now);
                            }
                            catch
                            {
                                // just catch and ignore with a short pause...
                                System.Threading.Thread.Sleep(5000);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
        {
            try
            {
                string message = string.Empty;
                string sTrace = ex.StackTrace;
                while (ex != null)
                {
                    message = message + Environment.NewLine + Environment.NewLine + ex.Message;
                    ex = ex.InnerException;
                }
                message = message + Environment.NewLine + Environment.NewLine + sTrace;

                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex2)
            {
                WriteToEventLog(ex2, EventLogEntryType.Error);
            }
        }
        private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
        {
            try
            {
                this.EventLog.WriteEntry(message, eventLogEntryType);
            }
            catch (Exception ex)
            {
                WriteToEventLog(ex, EventLogEntryType.Error);
            }
        }
        #endregion
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

倾听心声的旋律 2024-10-05 07:46:15

即使它作为发行版 exe 运行,当应用程序崩溃时,您仍然可以选择附加到调试器...您只是看不到调试符号,只看到程序集:)

我相信这是 Dr. Watson 进程捕获应用程序错误以进行调试...因为您的应用程序是一项服务,Dr. Watson 无法与桌面交互,从而给您带来您看到的错误。您可以转到服务属性并标记“允许服务与桌面交互”(位于“登录”选项卡上),当应用程序崩溃时,该选项卡应该会向您显示 Dr. Watson 弹出窗口。

禁用 Dr. Watson 的步骤如下:
http://support.microsoft.com/kb/188296

如果您想在服务器,您可以在服务器上启用远程调试,并将 Visual Studio 附加到进程...如果您想尝试此操作,我可以为您提供更多远程调试 Windows 服务的提示。

哈特哈,
詹姆斯

* 编辑 *
根据您提供的代码,我会查看以下区域:

  1. AppSettings.Default.FTPRootPathApp.Config 中设置是否正确?

  2. 服务启动时该目录是否立即发生变化?您有一个计时器被注释为“每五分钟检查一次”,这有点令人困惑,因为一旦您将 EnableRaisingEvents 设置为 true,FileSystemWatcher 就会开始接收事件。因此,问题实际上可能在于 fileCreatedOrChanged

  3. 沿着这些思路,您有一个 BackgroundWorker 为多个事件提供服务,更糟糕的是,您正在异步触发处理程序。这是我最有可能的怀疑,因为如果您在第一个作业运行时再次调用 _worker.RunWorkerAsync(),您将收到 InvalidOperationException。虽然我不确定为什么您不会在日志中看到这一点

  4. 您正在使用计时器来更新监视目录中所有文件的上次写入时间,并且每五秒执行一次。这似乎是一个非常糟糕的主意......我不确定你想要完成什么。这将触发您的 FileSystemWatcher 的更改事件,这可以解释为什么您在启动后不到 10 秒就崩溃了(计时器的初始滴答设置为立即触发,这意味着 5 秒后您将崩溃)更改所有文件时间,此后不久多次触发 FileSystemWatcher

所以我最好的猜测是,在五秒内,您已经开始触发多个 RunWorkAsync() 调用相同的 BackgroundWorker,这是一个禁忌:)

将静态变量 _isBusy 设置为 true/false 是不可靠的,因为您与 BackgroundWorkers 进行多线程...您需要使用互斥锁或其他锁,但这是否真的违背了使用BackgroundWorker 的目的?

另外,如果您想使用 isBusy 标志之类的东西,它必须看起来更像是:

while (_isBusy) {
    System.Threading.Thread.Sleep(5000);
}

_isBusy = true;
_worker.RunWorkerAsync(eventArgs);

在尝试启动后台工作程序之前,您需要 _isBusy 为 false...按照您的方式,如果事件触发 100 次,您将拨打 100 个电话。

解决您的问题的最简单的解决方案是每次事件触发时在 fileCreatedOrChanged 方法中创建一个新的BackgroundWorker...创建如此多的新线程会产生开销,但如果在此方法中完成的工作很重要,那么它将是值得付出的开销。

您也许可以依赖内置的BackgroundWorker.IsBusy 属性,但是,如果您只是要阻塞直到后台工作程序完成,那么我不得不再次质疑异步线程的好处。

** 编辑 **

我现在明白您试图通过初始文件时间戳更改来完成什么...我认为您最好不要保留时间戳,而只需运行启动循环来处理现有文件。您可以为每个线程生成一个后台工作线程,就像在 FileSystemWatcher 通知上所做的那样。你处理它的方式是故意创造一个副作用来触发你想要的结果。

在日益增长的复杂性中,我有点失去了踪迹……整个队列/出队的事情可能是不必要的。或者也许我只是没有看到真正存在的需求。同样,令我印象深刻的是,您异步启动后台工作程序,然后将主线程置于睡眠状态,直到完成。

当您将主线程置于睡眠状态时,不会处理任何事件,因此您真正将多线程限制为一个线程。我看到您想将线程完成所需的时间写入事件日志。如果有机会的话,我将开始第二个答案来解决这个问题,但其要点是传递一个 Stopwatch 类(它将为您提供通过的毫秒或 CPU 滴答的准确计数)在操作期间)添加到 DoWorkEventArgs.Result 属性。

但你要求的代码!基本上,无论您在何处决定调用 _worker.RunWorkerAsync(queuedFile),而不是每次运行一个类级 BackgroundWorker 都会创建一个新的。为事件处理程序等传递所有相同的参数。您的服务入口点将删除所有 BGW 引用,如下所示:

protected override void OnStart(string[] args)
{
    try
    {
        _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

        _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
        _watcher.IncludeSubdirectories = true;
        _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
        _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
        _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
        _watcher.EnableRaisingEvents = true;

        WriteToEventLog("Exit Start", EventLogEntryType.Information);
    }

并且异步运行 BGW 的代码将变为:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
worker.ProgressChanged += BackgroundWorkerProgressChanged;        // Note you don't need
worker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted;  // the 'new' here

worker.RunWorkerAsync(queuedFile);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //

Even though it's running as a release exe, you'll still be given the option to attach to a debugger when the app crashes... you just won't see debug symbols, just assembly :)

I believe it's the Dr. Watson process that catches app errors for debugging... Because your app is a service, Dr. Watson can't interact with the desktop, giving you the error you see. You can go to the service properties and mark "allow service to interact with the desktop", found on the LogOn tab, which should then give you a Dr. Watson popup when the app crashes.

Steps to disable Dr. Watson are here:
http://support.microsoft.com/kb/188296

If you want to debug the app on the server, you can enable remote debugging on the server, and attach Visual Studio to the process... if you want to try this, I can give you more tips for debugging a windows service remotely.

HTH,
James

* Edit *
Based on the code you provided, I'd look at the following areas:

  1. Is AppSettings.Default.FTPRootPath set correctly in App.Config?

  2. Are there changes happening to that directory immediately when the service starts? You have a timer commented as "check every five minutes", which is a little confusing, because the FileSystemWatcher will start receiving events as soon as you set EnableRaisingEvents to true. So the issue could actually lie within fileCreatedOrChanged

  3. Along those lines, you have one BackgroundWorker servicing multiple events, and worse, you're firing the handler asynchronously. This is my most likely suspect, because if you call _worker.RunWorkerAsync() again while the first job is running, you'll get an InvalidOperationException. Though I'm not sure why you wouldn't see that in the log

  4. You're using the timer to update the last write time for all files in the watched directory, and you do this every five seconds. This seems like a very bad idea... I'm not sure what you're trying to accomplish. This will fire your FileSystemWatcher's changed event, which would explain why you're crashing less than 10 seconds after you start (the timer's initial tick is set to fire immediately, meaning five seconds later you're changing all the file times, triggering the FileSystemWatcher multiple times shortly after that)

So my best guess is that within five seconds, you've begun firing multiple RunWorkAsync() calls on the same BackgroundWorker, which is a no-no : )

Setting the static variable _isBusy to true/false isn't reliable because you're multi-threading with the BackgroundWorkers... you need to use a Mutex or some other lock, but doesn't that really defeat the purpose of using a BackgroundWorker?

Also, if you wanted to use something like an isBusy flag, it would have to look more like:

while (_isBusy) {
    System.Threading.Thread.Sleep(5000);
}

_isBusy = true;
_worker.RunWorkerAsync(eventArgs);

You need _isBusy to be false before you try to launch the Background worker... the way you have it, if the event fires 100 times, you'll make 100 calls.

The easiest solution to your problem would be to create a new BackgroundWorker in the fileCreatedOrChanged method every time the event fires... there's overhead involved in creating so many new threads, but if the work being done in this method is significant, it will be worth the overhead.

You might be able to rely on the built-in BackgroundWorker.IsBusy property, but again, I'd have to question the benefit of asynchronous threading if you're just going to block until the background worker completes.

** Edit **

I understand now what you're trying to accomplish with the initial file timestamp changes... I think you would do better to leave the timestamps alone, but just run through a startup loop to process existing files. You can spawn a background worker thread for each one, just like you do on the FileSystemWatcher nofications. The way you're handling it is deliberately creating a side-effect to trigger the result you want.

I'm losing track a little bit in the growing complexity... the whole queue/dequeue thing might be unnecessary. Or maybe I just am not seeing a need that is truly there. Again, what strikes me is that you are launching the background worker asynchronously, then putting the main thread to sleep until it finishes.

When you put the main thread to sleep, no events will get processed, so you are truly throttling the multi-threading to one thread. I see that you want to write to the event log how long it took for a thread to finish. I will start a second answer to address that if I get a chance to, but the gist of it is to pass a Stopwatch class (which will give you an accurate count of either milliseconds or CPU ticks that pass during an operation) to the DoWorkEventArgs.Result property.

But the code you requested! Basically, wherever you decide to call _worker.RunWorkerAsync(queuedFile), rather than run one class-level BackgroundWorker create a new one each time. Pass all the same parameters for the event handlers, etc. Your service entry point would drop all the BGW references and look like:

protected override void OnStart(string[] args)
{
    try
    {
        _keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });

        _watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
        _watcher.IncludeSubdirectories = true;
        _watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
        _watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
        _watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
        _watcher.EnableRaisingEvents = true;

        WriteToEventLog("Exit Start", EventLogEntryType.Information);
    }

and the code where you run the BGW asynchronously would become:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
worker.ProgressChanged += BackgroundWorkerProgressChanged;        // Note you don't need
worker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted;  // the 'new' here

worker.RunWorkerAsync(queuedFile);  // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
梦里南柯 2024-10-05 07:46:15

错误消息告诉您它无法附加调试器来让您检查异常。这与这是一个发布版本这一事实完全无关。发布版本和调试版本都可以进行调试(幸运的是!)。

调试服务与调试常规应用程序略有不同。请查看本指南获取一些建议。

The error message tells you that it couldn't attach a debugger to let you inspect the exception. This is completely unrelated to the fact that this is a release build. Release build and debug builds can both be debugged (fortunately!).

Debugging services is a little different from debugging regular applications. Please check this guide for some advice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文