-1

I have to create a WPF App (.NET Framework) to create a program with user interface.

While the GUI is working, the program has to continue, with an infinite loop, to read in background a folder which is continuously filled with new txt files from an external program.

An example:

  • External programm --> creates new txt files and puts them into a folder named "Buffer"
  • My Programm --> starts in background an infinite loop that reads every single file and deletes them from "Buffer"

This process has to be controlled by the main GUI, but it doesn't have to stop the other processes.

I suppose that I have to use threads to parallelize the processes, but unfortunately I don't have a lot of experience with C#, could you help me by giving some suggestion please?

  • Forget "infinite loop". You either want to start a process "run" via for example Timer or maybe use [FileSystemWatcher](https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher?view=netframework-4.8) (<- Event-Based). – Fildor Feb 25 '21 at 10:53
  • Heads up: You'll probably sooner or later find that you are trying to process files that are still "in transit". You might want to consider this from the get-go. – Fildor Feb 25 '21 at 10:55
  • [Task.Run()](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=net-5.0) will start a new thread. Do your folder work in there and use [Dispatcher.BeginInvoke()](https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher.begininvoke?view=net-5.0) when you want to call back into the UI thread (to update progress or something) – GazTheDestroyer Feb 25 '21 at 10:59
  • 2
    @GazTheDestroyer I'd recommend [`IProgress`](https://learn.microsoft.com/en-us/dotnet/api/system.iprogress-1?view=netframework-4.8) / [`Progress`](https://learn.microsoft.com/en-us/dotnet/api/system.progress-1?view=netframework-4.8) for exactly that. – Fildor Feb 25 '21 at 11:00
  • 1
    Maybe another thing you could be interested in is [Dataflow](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library). – Fildor Feb 25 '21 at 11:01
  • You may find this helpful: [Run async method regularly with specified interval](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval) – Theodor Zoulias Feb 25 '21 at 11:04
  • 1
    @GazTheDestroyer no, Task.Run won't start a new thread, it will use a thread from the threadpool. The same way `Dispatcher.BeginInvoke` kind-of-did before tasks were introduced in 2010. There's no need to use `BeginInvoke` any more, definitely not since 2012 when `await` was introduced – Panagiotis Kanavos Feb 25 '21 at 11:20
  • @PanagiotisKanavos OP is clearly new to threading, so I presented the easiest way to run a delegate on a different thread without getting into the minutiae of thread lifetime. I still regularly use both await and BeginInvoke. Both have their uses. I agree Progress is a better approach for OP. – GazTheDestroyer Feb 25 '21 at 12:29

2 Answers2

0

I have been in your shoes, before. I cannot share the actual code (of course) but this is what I did:

  • Separate "Watching a Folder" at least into its own class (if not sub-project).
  • Separate "Process a file" at least into its own class(es).

For the Folder-Part:

  1. I used FileSystemWatcher to get notified of new files present in a folder. All the EventHandler did was to put the filepath into a ConcurrentDictionary<string,DateTime>, storing "last seen" along with the path as key.

  2. A timer that ran every (I think it was) 5 seconds filtered the Dictionary for all entries older than 2.5 seconds (half interval). This was to make sure, the file is not in transit anymore. Not really a "100% solution" but it worked well enough.

    The selected files (path strings) were set into a "blacklist" to be ignored in following runs and also sent to a Dataflow pipeline.

Then Processing:

  1. a. The data flow pipeline did all the file processing and finally signalled fnished process, so the file could be moved to a "done" folder and removed from blacklist.

    b. Files that caused exceptions were put in a "manual interaction needed" Folder along with a corresponding "filename.error" file that contained exception message and stacktrace and was removed from blacklist. Mind that you might have to handle duplicate filenames, there.

The only distinction was that I had to do this in a Console App, that ran continuously on a server. Later on I refactored it to be an actual Windows Service. You however have to deal with WPF. The basic idea should stay the same, though. All you'll probably need is a proper way to display the process and interact with it. I strongly recommend to follow the MVVM pattern for that.

I also strongly recommend to adopt one of the beautiful logging frameworks out there.

Fildor
  • 14,510
  • 4
  • 35
  • 67
-1

If you really need an infinite loop doing some background work on files, you are not required to use threads explicitly with modern .NET. You may use TPL and launch a background task that runs the infinite loop once your app starts up. In the infinite loop, you should periodically check the cancellation token to enable graceful completion of the background task.

I think it makes sence to refer to the TPL documentation: https://learn.microsoft.com/en-US/dotnet/standard/parallel-programming/task-parallel-library-tpl

High-level code sample:

// Start your background task
void StartBackgroundWorker()
{
  // _cts is a data members of some object that encapsulates the background activity
  _cts = new CancellationTokenSource();
  System.Threading.Tasks.Task.Run(() => TaskEntryPoint(cts.Token));
}

// Stop your background task
void StopBackgroundWorker()
{
  _cts.Cancel();
  _cts.Dispose();
}

// In the background task, check for cancellation
void TaskEntryPoint(CancellationToken token)
{
  while (!token.IsCancellationRequested)
  {
    // complete a chunk of your background processing work
    // and check whether cancellation was requested by the 
    // owner of the background worker
  }
}
Alexey Adadurov
  • 174
  • 1
  • 5
  • 2
    This code isn't needed. It treats tasks as if they were threads too. Tasks aren't threads, they have no entry points. Storing tasks in fields is bad practices. There's no reason to run the loop inside a task. All this code can be replaced with `while(!token.IsCancellationRequested){ await Task.Run(()=>someHeavyWork);}` – Panagiotis Kanavos Feb 25 '21 at 11:17
  • 2
    Not my downvote but ... What you posted is an attempt to rewrite BackgroundWorker using tasks. The problem with BGW isn't the implementation though, it's the entire concept – Panagiotis Kanavos Feb 25 '21 at 11:18
  • 3
    Not a downvoter either, but [don't use](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/) the `Task.Factory.StartNew` method. Use the `Task.Run` instead. Also don't check the `token.IsCancellationRequested` condition, because it usually results to inconsistent cancellation behavior. Use the `ThrowIfCancellationRequested` instead. According to the standard cancellation pattern, honoring a cancellation signal is communicated with a `OperationCanceledException` that is thrown by the canceled operation. – Theodor Zoulias Feb 25 '21 at 11:31