0

I have a background task in my software which should run indefinitely.

The code repeating the task (and possibly other tasks in the future) looks like this:

public class RunTicketTasks
{
    public async Task RunBackgroundTasks()
    {
        AssignTickets assignTickets = new AssignTickets();
        while (true)
        {
            await assignTickets.AssignTicketCreator();
            await Task.Delay(5 * 60 * 1000);
        }
    }
}

At the same time I have the WPF UI MainWindow.xaml which is the application entry point as far as I know.

within public MainWindow I start the task like the following way:

public MainWindow()
{ 
    InitializeComponent();
    JiraBackgroundTasks.RunTicketTasks ticketTasks = new JiraBackgroundTasks.RunTicketTasks();
    ticketTasks.RunBackgroundTasks();
}

Apparently, this starts the task on the same thread (I believe). The task is started and running successfully, but the UI is beeing blocked due to long running operations. Meanwhile, when I uncomment the last line ticketTasks.RunBackgroundTasks(); the UI just runs fine.

How do I start the task in the background so that my User Interface is still responsive?

EDIT:
The reason I started the task this way was because of exception handling. I can successfully start the task on a different thread as suggested with Task.Run(async () => await ticketTasks.RunBackgroundTasks()); But then I will not receive any exception if something goes wrong.

How can I start the task not blocking the UI and still receive the exception details if an exception is thrown?

The Internet states the following method:

await Task.Run(() => ticketTasks.RunBackgroundTasks());

But this will not work because The await operator can only be used within an async method.

julian bechtold
  • 1,875
  • 2
  • 19
  • 49
  • 3
    There doesn't seem to be anything async about the first snippet. But if it were, you're supposed to use `await Task.Delay` instead of `Thread.Sleep`. _BUT_ outside that, I'd recommend to use a Timer or even Scheduling Framework instead for recurring Tasks. – Fildor Apr 06 '21 at 07:44
  • thank you for the comment with Task.Delay I implemented it. The issue however is not the task.delay. I have long running operations here which block the UI Thread. – julian bechtold Apr 06 '21 at 07:49
  • Simple writing of async Task does not make your code work in background. You should read more basics about async await and Task – Sir Rufo Apr 06 '21 at 07:52
  • 1
    Well, you are not creating (or pulling from pool) a separate Thread here. As a first step, you may want to have a look into `Task.Run`. – Fildor Apr 06 '21 at 07:53
  • 1
    Agree with @Fildor that you'd better not use `Task`s for such infinite background processing, but solution for you is `Task.Run(async () => await ticketTasks.RunBackgroundTasks());`. – Alexey Rumyantsev Apr 06 '21 at 08:01
  • thank you all for the suggestion. Task.Run indeed does the trick. – julian bechtold Apr 06 '21 at 08:02
  • What does the `AssignTicketCreator` do? Does by any chance interact with UI controls? In that case you can't just wrap it in a `Task.Run`, because the UI components should only be accessed by the UI thread. As a side note, to get a consistent 5 minute interval you should create the `Task.Delay` task before the `AssignTicketCreator` call, and `await` it afterwards, as shown [here](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval/62724908#62724908). – Theodor Zoulias Apr 06 '21 at 08:19
  • @TheodorZoulias the background tasks do not interact with Ui in any way. In this case the background task connects to the ticket system in order to assign tickets created with the software. The delay does not have to be consistent by any means. It's just there to prevent overload of my cpu and our ticket system api. Otherwise I would have made some basic sheduling with system.DateTime – julian bechtold Apr 06 '21 at 08:40
  • 1
    OK. One more note: the task returned by the `RunBackgroundTasks` method is not awaited (it is a fire-and-forget task), so a possible exception will not be observed. If you want to be notified when the task completes, you should `await` the task. Alternatively you could convert the method to `async void` instead of `async Task`, so that it has event handler semantics (unhandled exceptions on event handlers result to the crashing of the app).¨ – Theodor Zoulias Apr 06 '21 at 09:04
  • @TheodorZoulias this is a good catch. the await would make the task run synchronously, blocking the ui thread again no? I think this is the exact reason I had `ticketTasks.RunBackgroundTasks().Wait()` in the beginning (which blocked the ui) – julian bechtold Apr 06 '21 at 09:15
  • 1
    Nope, the `await` does not block the UI thread. Await means asynchronous wait. In simple terms it attaches a continuation to the task, that contains all code that follows the `await` keyword. That code will run when the task completes. What blocks the UI thread is the `Wait` method, which should be avoided in general for exactly this reason. – Theodor Zoulias Apr 06 '21 at 09:21

1 Answers1

0

One way to do it is to wrap the task in an async event callback:


public partial class MainWindow : Window
{
    // I mocked your missing types, replace with your own 
    class AssignTickets
    {
        public Task AssignTicketCreator() => Task.CompletedTask;
    }

    public class RunTicketTasks
    {
        public async Task RunBackgroundTasks()
        {
            AssignTickets assignTickets = new AssignTickets();
            int x = 0;
            while (true)
            {
                await assignTickets.AssignTicketCreator();
                await Task.Delay(1000);

                var isRunningOnDispatcher = Application.Current.Dispatcher ==
                    System.Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread);
                Trace.WriteLine($"One second passed, running on dispatcher: {isRunningOnDispatcher}");

                // exception will also get thrown on the main thread
                if (x++ > 3) throw new Exception("Hello!");
            }
        }
    }

    public MainWindow()
    {
        InitializeComponent();

        // event fires on UI thread!
        this.Loaded += async (sender, args) =>
        {
            try
            {
                await new RunTicketTasks().RunBackgroundTasks();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        };
    }
}

sneusse
  • 1,422
  • 1
  • 10
  • 18