0

I am trying to develop a simple application that must have GUI components. It will be a service in the taskbar tray but must query the database every few minutes to check for changes, then post to a web server the results. It will run 24/7.

This is my first application, and so have been getting some help along the way from SO. When I first used the FluentScheduler, I had trouble (C# FluentScheduler Job not Repeating) but got it working as a simple proof of concept with a console app.

As I tried taking what I had learned and implementing it with my Windows Forms solution, I couldn't get it working at all because once it ran the Application.Run(ThisForm); command, the scheduler did nothing. Eventually while troubleshooting, I stumbled across this: https://github.com/fluentscheduler/FluentScheduler/issues/169

I see that you're using the library from something like a Windows Forms/WPF application. Starting threads/tasks from a GUI application is a pain in the ***, maybe that's what's biting you. Fingers crossed to be something else, diving up on STA threads, dispatchers, synchronization contexts and alikes is no fun.

So now I am left wondering what I am supposed to do? Am I supposed to develop the scheduled tasks as a console app leaving an API for a WPF application to communicate with, or am I supposed to work through the pain he is describing and make it work within WPF?

As this is my first C# project, it seems pretty complicated to separate the two components, but am willing to learn if that is the right choice. I am still very early in the project just doing proof of concepts of each needed feature and so can easily switch to WPF, UWP, or whatever else is most appropriate. It will have minimal GUI, just a few forms to fill out username / password type stuff and options to sync.

Even though this FluentScheduler has about a quarter million downloads, maybe there is a better one that doesn't suffer from the same limitations you could recommend.

Alan
  • 2,046
  • 2
  • 20
  • 43
  • 1
    This question is too open ended to give a answer. What I can say you: WPF (and it's followup, UWP) were designed with the MVVM pattern in mind. While you can use Windows Forms and other pattern approaches when working with it, you would miss most of the power of WPF doing so. Years ago I wrote a short intro into MVVM, maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf If you do not want to learn this first, you might be better off staying at Windows Forms for the time being. – Christopher Jan 08 '18 at 18:42
  • 1
    Have you set a breakpoint inside your scheduled callback to verify that it is not being invoked? It's not clear to me from the GitHub issue whether your app is failing because the job is never executed, or because the worker thread is attempting to interact with WPF components, triggering an exception. – Mike Strobel Jan 08 '18 at 18:43
  • @Mike, I did indeed set a breakpoint and anything located below the `Application.Run(ThisForm);` isn't executed until you close the application. I also tried putting it above, but it would only run once, not every 2 seconds as directed. I did trigger some exceptions about the worker thread, but I felt that was a separate issue. – Alan Jan 08 '18 at 21:23
  • @Christopher I know it seems a broad question and will look at your intro. I don't mind learning the MVVC, I was just hoping to get a nudge in the right direction. – Alan Jan 08 '18 at 21:25
  • 1
    @Alan Your application 'stops' there because that's where it enters the message loop. That's expected with any WPF application; it won't exit that method until the application is closed. However, the main thread will still be doing work: it will handle input events, layout, rendering, and any custom actions you've scheduled to run on the `Dispatcher`. To see if the scheduled job is running, you need to set a breakpoint _in the method (or lambda) the scheduler is supposed to call_. – Mike Strobel Jan 08 '18 at 21:27
  • @MikeStrobel Thank you for that explanation. I stumbled on a project that looks to be a great starting point (https://www.codeproject.com/Articles/1173686/A-Csharp-System-Tray-Application-using-WPF-Forms) What would be a location that such a job might be inserted? My own project is stupidly primitive at this point, just a WPF with a TextBox that is supposed to append "HELLO" to every 2 seconds, but even that can't be shared on .netfiddle, so I thought this might be easier. – Alan Jan 08 '18 at 21:55

1 Answers1

1

Based on the earlier post you linked to, I see a few problems with your code:

  1. Your call to JobManager.Initialize is unreachable because it occurs after Application.Run, which blocks until the application shuts down (e.g., when the last window is closed).

  2. The FluentScheduler will schedule your job to run on an arbitrary worker thread, but your action accesses or manipulates UI elements. In both WPF and Windows Forms, you can only touch UI elements from the main thread. If your job needs to touch the UI, it must first marshal itself back onto the UI thread.

  3. The scheduled action in your original post does not make sense:

     Action someMethod = new Action(() =>
                             {
                                 Form1 ThisForm = new Form1();
                                 ThisForm.Text ="HELLO";
                             });
    

    Specifically, you are creating a new window that is never shown, rather than modifying one that already exists.

Here is a simple example project that you should be able to use as a starting point. It displays the current time, updating once per second. I used WPF, as I haven't used Windows Forms in years, and there's no compelling reason to use it these days.

SchedulerText.xaml:

<Window x:Class="WpfTest.SchedulerTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <TextBlock x:Name="_textBlock"
               FontSize="18pt"
               TextAlignment="Center"
               VerticalAlignment="Center" />
  </Grid>
</Window>

SchedulerTest.xaml.cs:

using System;
using FluentScheduler;

namespace WpfTest
{
    public partial class SchedulerTest
    {
        public SchedulerTest()
        {
            InitializeComponent();

            JobManager.AddJob(
                this.DoScheduledWork,
                schedule => schedule.ToRunNow().AndEvery(1).Seconds());
        }

        private void DoScheduledWork()
        {
            // Go query your database, or do whatever your main job is.
            // You don't want to do this on the UI thread, because it
            // will block the thread and prevent user interaction.
            DoPrimaryWorkOffUIThread();

            // If you need to communicate some sort of result to the user,
            // do it on the UI thread.
            Dispatcher.Invoke(new Action(ShowResultsOnUIThread));
        }

        private DateTime _currentResult;

        private void DoPrimaryWorkOffUIThread()
        {
            _currentResult = DateTime.Now;
        }

        private void ShowResultsOnUIThread()
        {
            _textBlock.Text = $"{_currentResult:h:mm:ss}";
        }
    }
}

Note that you don't have to initialize the job in the windows's constructor, but that would be the easiest place to do it.

Mike Strobel
  • 25,075
  • 57
  • 69