سؤال

ولقد التعليمة البرمجية التالية:


Imports System.IO

Public Class Blah
    Public Sub New()
        InitializeComponent()

        Dim watcher As New FileSystemWatcher("C:\")
        watcher.EnableRaisingEvents = True

        AddHandler watcher.Changed, AddressOf watcher_Changed
    End Sub

    Private Sub watcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs)
        MsgBox(e.FullPath)
    End Sub
End Class

عند I تشغيله وحفظ التغييرات إلى ملف على القرص C بلدي، رمز يعمل كبيرة، إلا أنه يقوم بتنفيذ الأسلوب watcher_Changed () أربع مرات. أي فكرة لماذا؟ وchangeType هو "4" في كل مرة.

وشكرا.

هل كانت مفيدة؟

المحلول

ومن قسم "استكشاف الأخطاء وإصلاحها FileSystemWatcher مكونات" من الوثائق VS.NET ...

<اقتباس فقرة>   

الأحداث مكون متعددة ولدت لعمل واحدة

     

وأنت قد تلاحظ في بعض الحالات التي حدث خلق واحد يولد متعددة أحداث مكون التي يتم التعامل معها من قبل مكون الخاص بك. على سبيل المثال، إذا كنت تستخدم أحد مكونات FileSystemWatcher لمراقبة إنشاء ملفات جديدة في الدليل، ومن ثم اختباره باستخدام المفكرة لإنشاء ملف، قد ترى حدثين مكون ولدت حتى ولو تم إنشاؤها فقط ملف واحد. وذلك لأن المفكرة بتنفيذ الإجراءات نظام الملفات متعددة خلال عملية الكتابة. المفكرة يكتب إلى القرص على دفعات التي تخلق محتوى الملف ثم سمات الملف. قد تؤدي التطبيقات الأخرى بنفس الطريقة. لأن FileSystemWatcher تراقب أنشطة نظام التشغيل، وجميع الأحداث التي هذه التطبيقات النار سوف يتم انتقاؤها.

     

ملحوظة: قد المفكرة أيضا أن يسبب أجيال الحدث أخرى مثيرة للاهتمام. على سبيل المثال، إذا كنت تستخدم ChangeEventFilter لتحديد أن كنت ترغب في مشاهدة فقط لإجراء تغييرات السمة، ثم الكتابة إلى الملف في الدليل الذي تشاهده باستخدام المفكرة، وقمت برفع حدث. وذلك لأن المفكرة بتحديث سمة الأرشيف لملف خلال هذه العملية.

نصائح أخرى

وقبل فترة، لقد تجربة نفس المشكلة.

وبعد بعض البحث thtrough شبكة الإنترنت، ويبدو أنني لم أكن الوحيد جود هذه القضية. :) لذلك، ربما هو خلل في FileSystemWatcher ...

ولقد حلها من خلال تعقب آخر مرة تم رفع eventhandler. إذا كان قد تم رفعه أقل ثم قبل ميللي ثانية الثلاثون، أعود من وجهة نظري eventhandler. إذا كان أي شخص يعرف الإصلاح الذي هو أكثر أناقة. بلز اسمحوا لي أن أعرف. :)

وهذه هي الطريقة التي عملت من حوله:

if( e.ChangeType == WatcherChangeTypes.Changed )
{

    // There is a nasty bug in the FileSystemWatch which causes the 
    // events of the FileSystemWatcher to be called twice.
    // There are a lot of resources about this to be found on the Internet,
    // but there are no real solutions.
    // Therefore, this workaround is necessary: 
    // If the last time that the event has been raised is only a few msec away, 
    // we ignore it.
    if( DateTime.Now.Subtract (_lastTimeFileWatcherEventRaised).TotalMilliseconds < 500 )
    {
        return;
    }


    _lastTimeFileWatcherEventRaised = DateTime.Now;


    .. handle event

وبلدي حل لهذه المشكلة هو قليلا مثل إريكس] إلا أنني استخدام System.Windows.Forms.Timer بدلا من البدء في موضوع جديد. والفكرة هي أن أتعامل مع هذا الحدث التغيير فقط عندما مرت س مللي ثانية بدون أي ملف تغير الأحداث. لاحظ أن كل شيء يحدث على موضوع واجهة المستخدم الرسومية حتى لا تكون هناك قضايا الترابط. يمكنني استخدام س = 100.

    private Dictionary<String, FileSystemEventArgs> xmlFileChangedEvents = new Dictionary<string, FileSystemEventArgs>();
    private void debugXmlWatcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (!xmlFileChangedEvents.ContainsKey(e.Name))
            xmlFileChangedEvents.Add(e.Name, e);
        xmlChangeTimer.Stop();//Reset the Forms.Timer so that it times out in 100 ms
        xmlChangeTimer.Start();
    }

    private void xmlChangeTimer_Tick(object sender, EventArgs e)
    {
        foreach (FileSystemEventArgs eventArg in xmlFileChangedEvents.Values)
        {
            //
            //Handle the file changed event here
            //
        }
        xmlFileChangedEvents.Clear();
    }

وسوف مراقب تغير معالج الحدث يطلقون النار على 3 احداث ... خلق، وحذف، وتغير. فقط عند إعادة تسمية ملف سوف النار الحدث onrenamed. ربما كان هذا هو السبب في أنك تحصل على 4 إنذارات. أيضا معظم برامج تشغيل عمليات متعددة على الملف قبل إغلاقه. يعتبر كل حدث تغيير وهكذا يتم تشغيل الحدث on_changed في كل مرة.

وعلى افتراض الطريق هو نفسه في كل مرة، هل من الممكن أن البرنامج الذي تستخدمه لحفظ الملف يقوم به في الواقع إلا في قطع؟ أو هل لديك Blah أكثر من مثيل؟


وتحرير: هل لديك أي مكافحة الفيروسات لصناعة السيارات في حماية البرامج التي تعمل؟ تلك قد لمس ملف في هذه العملية.

MSDN الوثائق :

<اقتباس فقرة>   قد

وعمليات نظام الملفات المشترك   رفع الحدث أكثر من واحد. إلى عن على   سبيل المثال، عندما يتم نقل ملف من واحد   الدليل إلى آخر، عدة   OnChanged وبعض OnCreated و   قد تثار الأحداث OnDeleted.   نقل ملف هي عملية معقدة   التي تتكون من عدة بسيطة   العمليات متعددة بالتالي رفع   الأحداث. وبالمثل، فإن بعض التطبيقات   (على سبيل المثال، برامج الحماية من الفيروسات)   قد يسبب نظام ملف إضافي   الأحداث التي يتم الكشف عنها من قبل   FileSystemWatcher.


وتحرير: أو ربما هناك شيء يجب القيام به مع كيف النوافذ وحفظ الملف. قد يكون الحصول على أكثر من حدث من تغييرات مختلفة. (واحد لحجم واحد لآخر الكتابة الطابع الزمني، واحدة للحصول الطابع الزمني الماضي، وأحد أكثر ل... شيء آخر.) حاول تعيين الخاصية FileSystemWatcher وNotifyFilter لنوع واحد من التغيير ومعرفة ما اذا كان الاستمرار في الحصول على أحداث متعددة.

وهناك احتمال آخر، والتي تبذل خطأ :) ربما كنت مثيل وإنهاء بك "بلاه" الطبقة قبل استخدامه للfilewatching الغرض، ونسيان لتنفيذ RemoveHandler التي تخلص / أو أي طريقة teardown ذات الصلة. (؟)

وكتبت بعض التعليمات البرمجية التي يحل هذه المشكلة وغيرها من الميزات أنيق من FileSystemWatcher. لها نشر في بلدي بلوق في: http://precisionsoftware.blogspot.com /2009/05/filesystemwatcher-done-right.html

ولقد وجدت هذه الصفحة لنفس المشكلة. ومن ما يبدو، حتى إذا قمت بإضافة منطق لمعالجة مشروط أحداث متعددة، أي رمز التي من المفترض أن تتم معالجتها سوف تنقطع / إحباط عندما لاحق (مكررة) حدث ويحدث وبالتالي يسبب سلوك غير مرغوب فيه. أعتقد أن الطريق نحو هذا سيكون لتنفيذ معالج حدث في مؤشر ترابط مختلف نوعا ما ... نأمل أن يكون هذا الأمر يبدو معقولا تماما.

وابتهاج،

ونيكو

ولقد تقدمت فئة بسيطة أن يعمل بشكل جيد بالنسبة لي. قد يكون من المفيد لشخص آخر.

using System;
using System.IO;
using System.Timers;

namespace Demo
{
    class FileWatcher
    {
        private FileSystemWatcher watcher = new FileSystemWatcher();
        private Timer t = new Timer();

        public event EventHandler FileChanged;

        public FileWatcher()
        {
            t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
            t.Interval = 1000;
        }

        public void Start(String path)
        {
            watcher.Path = Path.GetDirectoryName(path);
            watcher.Filter = Path.GetFileName(path);
            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
            watcher.EnableRaisingEvents = true;
            watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (!t.Enabled)
                t.Start();
        }

        void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            t.Stop();
            if (FileChanged != null)
                FileChanged(this, null);
        }
    }
}

ويمكن استخدام مثل ما يلي:

FileWatcher FileWatcher1 = new FileWatcher();
FileWatcher1.FileChanged += new EventHandler(FileWatcher1_FileChanged);
FileWatcher1.Start("c:\test.txt");

ولقد كان هذا هوس مجن من API FindFirstChangeNotification () Win32 ومنذ يوم 1 (منذ ويندوز 3.X)، ويبدو FileSystemWatcher يلتف ببساطة أن API. النهج الموقت (المعروضة أعلاه) هو الحل المشترك.

وأنا عادة إنشاء فئة التي يلتف FileSystemWatcher ويفعل تصفية متعددة تغيير الطلب. وهناك القليل من العمل الاضافي للكتابة، لكنه يؤتي ثماره في إعادة استخدامها.

public class FileChangeMonitor
{
    private FileSystemWatcher _fsw;
    DateTime _lastEventTime;

    public event FileSystemEventHandler Changed;

    public FileChangeMonitor(string path, string filter)
    {
        _fsw = new FileSystemWatcher(path, filter);
        _fsw.Changed += new FileSystemEventHandler(_fsw_Changed);
        _fsw.EnableRaisingEvents = true;
        _fsw.NotifyFilter = NotifyFilters.LastWrite;
        _fsw.IncludeSubdirectories = false;
    }

    private void _fsw_Changed(object sender, FileSystemEventArgs e)
    {
        // Fix the FindFirstChangeNotification() double-call bug
        if (DateTime.Now.Subtract(_lastEventTime).TotalMilliseconds > 100)
        {
            _lastEventTime = DateTime.Now;
            if (this.Changed != null)
                this.Changed(sender, e);  // Bubble the event
        }
    }
}

ويمكنك بعد ذلك استخدام FileChangeMonitor الى حد كبير وكأنك FileSystemWatcher:

FileChangeMonitor fcm = new FileChangeMonitor(path, filter);
fsm.Changed += new FileSystemEventHandler(fsm_Changed);
...

وبطبيعة الحال، فإن رمز أعلاه فقط يعالج الحدث المتغيرة وNotifyFilters.LastWrite، ولكن تحصل على هذه الفكرة.

ومنصة خدعة المستقلة:

// Class level variable
bool m_FileSystemWatcherIsMessy = true;

// inside call back
if (m_FileSystemWatcherIsMessy) {
    m_FileSystemWatcherIsMessy = false;
    return;
} else {
    m_FileSystemWatcherIsMessy = true;
}

إذا كنت بحاجة إلى عرض أحداث التغيير في حين أنهم يحدث في نموذج، ثم تحتاج إلى استخدام خيوط. حل اريك هو الأفضل في هذا الصدد نظرا لأنه يمكن استخدامها بسهولة مع أو بدون شكل يجعل الحل أكثر مرونة. كما أنه يتعامل مع الأحداث مكررة متعددة لطيف ويتأكد من أنه يأكل فقط تكرار الأحداث إلا إذا كان لنفس الملف. في حل مقبول، إذا يتم تغيير ملفين في قرب الوقت نفسه، واحدة من المناسبات الخاصة بهم يمكن تجاهلها بشكل غير صحيح.

وهنا هو دليل على مفهوم كيف التعامل مع هذا.

لاختبار، إنشاء تطبيق ويندوز جديد نماذج. في النموذج، إضافة مربع نص متعدد الأسطر المسمى "tbMonitor". انقر بزر الماوس الأيمن على شكل واذهب الى عرض التعليمات البرمجية. استبدال هذا الرمز برمز لقد تضمنت أدناه. علما بأنني تعيين وقت الانتظار إلى عدد كبير حقا حتى تتمكن من اللعب مع حولها قليلا. في الإنتاج، سوف ربما كنت ترغب في جعل هذا الرقم أقل من ذلك بكثير، وربما حوالي 10 أو 15.

Imports System.IO
Imports System.Threading
Public Class Form1
Private Const MILLISECONDS_TO_WAIT As Integer = 1000
Private fw As FileSystemWatcher
Private Shared AccessEntries As List(Of String)
Private Delegate Sub UpdateBoxDelegate(ByVal msg As String)
Private Sub UpdateBox(ByVal msg As String)
    If tbMonitor.InvokeRequired Then
        Invoke(New UpdateBoxDelegate(AddressOf UpdateBox), New Object() {msg})
    Else
        tbMonitor.AppendText(msg + vbCrLf)
    End If
End Sub

Private Sub AccessEntryRemovalTimer(ByVal RawFileName As Object)
    UpdateBox("Sleeping to watch for " + RawFileName.ToString + " on thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
    Thread.Sleep(MILLISECONDS_TO_WAIT)
    AccessEntries.Remove(RawFileName.ToString)
    UpdateBox("Removed " + RawFileName.ToString + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
End Sub

Private Sub Changed(ByVal source As Object, ByVal e As FileSystemEventArgs)
    If AccessEntries.Contains(e.Name) Then
        UpdateBox("Ignoring a " + e.ChangeType.ToString + " notification for " + e.Name + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
        Return
    End If
    Dim AccessTimerThread As Thread

    AccessEntries.Add(e.Name)
    UpdateBox("Adding " + e.Name + " to the collection and starting the watch thread.")
    AccessTimerThread = New Thread(AddressOf AccessEntryRemovalTimer)
    AccessTimerThread.IsBackground = True
    AccessTimerThread.Start(e.Name)

End Sub

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    tbMonitor.ScrollBars = ScrollBars.Both
    AccessEntries = New List(Of String)
    fw = New FileSystemWatcher
    fw.Path = "C:\temp"
    fw.NotifyFilter = NotifyFilters.LastWrite Or NotifyFilters.LastAccess Or NotifyFilters.FileName
    AddHandler fw.Changed, AddressOf Changed
    AddHandler fw.Created, AddressOf Changed
    AddHandler fw.Renamed, AddressOf Changed
    fw.EnableRaisingEvents = True
End Sub

Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
    fw.EnableRaisingEvents = False
    RemoveHandler fw.Changed, AddressOf Changed
    RemoveHandler fw.Created, AddressOf Changed
    RemoveHandler fw.Renamed, AddressOf Changed
    fw.Dispose()
End Sub
End Class

وحل فريدريك هو من قبل حتى الآن أفضل شيء جئت عبر. ومع ذلك وجدت 500 ميلي ثانية لتكون بعيدة بطيئة جدا. في بلدي التطبيق المستخدم قادرا على أداء إجراءين على ملف بسهولة داخل 0،5 ثواني حتى أنا خفضت إلى 100 وحتى الآن انها تعمل خارج غرامة. صاحب C # كان قليلا fubar (فإنه يتم تحويل) حتى هنا في النسخة VB:

Public LastTimeFileWatcherEventRaised As DateTime

If DateTime.Now.Subtract(LastTimeFileWatcherEventRaised).TotalMilliseconds < 100 Then Return

LastTimeFileWatcherEventRaised = DateTime.Now

.. handle event here

ولقد ألهمت حل بلدي مع لاوس سبيل المثال هنا أعلاه. I تنفيذ مراقب للمجلد، وفي كل مرة يتم تشغيل ذلك، وأنا وقف مؤقت وتشغيله مرة أخرى إلى إعادة تعيينها. I النار أعمالي فقط عندما ينتهي الموقت، الذي منع مراقب من التسبب العمل مرتين لإنشاء الملف. وكما هو مطلوب، هو في VB.Net:)

    <PermissionSet(SecurityAction.Demand, Name:="FullTrust")> Public Sub StartWatcher()

    Dim watcher As FileSystemWatcher = New FileSystemWatcher()
    watcher.Path = _MWVM.TemplatesFolder

    'Watch for changes in LastWrite times, And the renaming of files Or directories. 
    watcher.NotifyFilter = NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName

    ' Only watch text files.
    watcher.Filter = "*.txt"

    'Define timer to 100 ms
    WatcherTimer.Interval = New TimeSpan(0, 0, 0, 0, 100) '100 ms

    ' Add event handlers.
    AddHandler watcher.Changed, AddressOf WatcherHandler
    AddHandler watcher.Created, AddressOf WatcherHandler
    AddHandler watcher.Deleted, AddressOf WatcherHandler
    AddHandler watcher.Renamed, AddressOf WatcherHandler

    ' Begin watching
    watcher.EnableRaisingEvents = True

End Sub

'Instantiate a timer which will prevent the 
Private WithEvents WatcherTimer As New System.Windows.Threading.DispatcherTimer
Private xmlFileChangedEvents As New Dictionary(Of String, FileSystemEventArgs)

Private Sub WatcherHandler(ByVal Sender As Object, ByVal e As FileSystemEventArgs)
    WatcherTimer.Stop() 'Reset the timer
    WatcherTimer.Start()
End Sub

Private Sub WatcherTimer_Tick(ByVal Sender As Object, ByVal e As EventArgs) Handles WatcherTimer.Tick
    WatcherTimer.Stop()
    PopulateMailTemplateList()
End Sub
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top