How to make FileSystemWatcher events run in an STA Thread in a console application?
See the question and my original answer on StackOverflowHere is a solution based on a TaskScheduler, not related to Winforms nor WPF. It allows you to use all Task-related functions and tooling:
static void Main(string[] args)
{
// one instance only is needed
var scheduler = new SingleThreadTaskScheduler(thread =>
{
// configure it for STA
thread.SetApartmentState(System.Threading.ApartmentState.STA);
});
using var fsw = new FileSystemWatcher(@"c:\temp");
fsw.Created += onEvent;
fsw.Changed += onEvent;
fsw.Deleted += onEvent;
fsw.Renamed += onRenamed;
fsw.EnableRaisingEvents = true;
// press any key to stop
Console.ReadKey(true);
void onEvent(object sender, FileSystemEventArgs e)
{
Task.Factory.StartNew(() => { Console.WriteLine(e.FullPath); /* do something in STA */ }, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
void onRenamed(object sender, RenamedEventArgs e)
{
Task.Factory.StartNew(() => { Console.WriteLine(e.FullPath); /* do something in STA */ }, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
}
public sealed class SingleThreadTaskScheduler : TaskScheduler, IDisposable
{
private readonly AutoResetEvent _stop = new AutoResetEvent(false);
private readonly AutoResetEvent _dequeue = new AutoResetEvent(false);
private readonly ConcurrentQueue<Task> _tasks = new ConcurrentQueue<Task>();
private readonly Thread _thread;
public event EventHandler Executing;
public SingleThreadTaskScheduler(Action<Thread> threadConfigure = null)
{
_thread = new Thread(SafeThreadExecute) { IsBackground = true };
threadConfigure?.Invoke(_thread);
_thread.Start();
}
public DateTime LastDequeue { get; private set; }
public bool DequeueOnDispose { get; set; }
public int DisposeThreadJoinTimeout { get; set; } = 1000;
public int WaitTimeout { get; set; } = 1000;
public int DequeueTimeout { get; set; }
public int QueueCount => _tasks.Count;
public void ClearQueue() => Dequeue(false);
public bool TriggerDequeue()
{
if (DequeueTimeout <= 0)
return _dequeue != null && _dequeue.Set();
var ts = DateTime.Now - LastDequeue;
if (ts.TotalMilliseconds < DequeueTimeout)
return false;
LastDequeue = DateTime.Now;
return _dequeue != null && _dequeue.Set();
}
public void Dispose()
{
_stop.Set();
_stop.Dispose();
_dequeue.Dispose();
if (DequeueOnDispose)
{
Dequeue(true);
}
if (_thread != null && _thread.IsAlive)
{
_thread.Join(DisposeThreadJoinTimeout);
}
}
private int Dequeue(bool execute)
{
var count = 0;
do
{
if (!_tasks.TryDequeue(out var task))
break;
if (execute)
{
Executing?.Invoke(this, EventArgs.Empty);
TryExecuteTask(task);
}
count++;
}
while (true);
return count;
}
private void SafeThreadExecute()
{
try
{
ThreadExecute();
}
catch
{
// continue
}
}
private void ThreadExecute()
{
do
{
if (_stop == null || _dequeue == null)
return;
_ = Dequeue(true);
// note: Stop must be first in array (in case both events happen at the same exact time)
var i = WaitHandle.WaitAny(new[] { _stop, _dequeue }, WaitTimeout);
if (i == 0)
break;
// note: we can dequeue on _dequeue event, or on timeout
_ = Dequeue(true);
}
while (true);
}
protected override void QueueTask(Task task)
{
if (task == null)
throw new ArgumentNullException(nameof(task));
_tasks.Enqueue(task);
TriggerDequeue();
}
protected override IEnumerable<Task> GetScheduledTasks() => _tasks;
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false;
}