1

I have inherited a C#/XAML/Win 8 application. There is some code which is set to run every n seconds.

The code that sets that up is:

if(!_syncThreadStarted)
{
    await Task.Run(() => SyncToDatabase());
    _syncThreadStarted = true;
}

The above code is ran once.

And then inside SyncToDatabase() we have:

while (true)
{
    DatabaseSyncer dbSyncer = new DatabaseSyncer();
    await dbSyncer.DeserializeAndUpdate();

    await Task.Delay(10); // after elapsed time re-run above code
}

The method DeserializeAndUpdate queries a in-memory collection of objects and pushes those objects to a web service.

Sometimes the send request to the web service takes longer than expected meaning duplicate items are sent.

Question: Is there a way to have a thread or some type of thread pool/background worker which I can stop/abort/destroy inside the method SyncToDatabase() , and then initialize/start it once we are done? This will ensure no subsequent requests are fired while a previous request is still pending.

Edit: I am not very knowledgeable when it comes to Threads, but the logic I want is:

Create thread which runs some method every x seconds, and when it starts that thread stop the "running every x seconds" part, after thread has complete start the "run every x seconds" part again.

E.g. if the thread kicks off at 10:01:30AM and does not complete until 10:01:39AM (9 seconds) the next thread should start at 10:01:44AM (5 seconds after work completed) - does that make sense? I do not want 2 or more threads running at the same time.

Here is my code for the above:

var period = TimeSpan.FromSeconds(5);
var completed = true;
ThreadPoolTimer syncTimer = ThreadPoolTimer.CreatePeriodicTimer(async (source) =>
{
    // stop further threads from starting (in case this work takes longer than var period)
    syncTimer.Cancel();
    DatabaseSyncer dbSyncer = new DatabaseSyncer();
    await dbSyncer.DeserializeAndUpdate(); // makes webservices calls

    Dispatcher.RunAsync(CoreDispatcerPriority.High, async () =>
    {
        // Update UI
    }

    completed = true;
}, period,
(source) =>
{
    if(!completed)
    {
        syncTimer.Cancel(); // not sure if this is correct...
    }
}

Thanks, Andrew)

andrewb
  • 2,995
  • 7
  • 54
  • 95

2 Answers2

2

This is not specific to Windows 8. Usually Task.Run is used for CPU-bound work, to offload it to a pool thread and keep the UI (or the core service loop) responsive. In your case, as far as I can tell, the main payload is dbSyncer.DeserializeAndUpdate, which is already asynchronous and most likely network-IO bound, rather than CPU-bound.

Besides, the author of the original code does _syncThreadStarted = true after await Task.Run(() => SyncToDatabase()). That doesn't make sense, because the work on the pool thread would have been already done by the time _syncThreadStarted = true is executed, thanks to the await.

To cancel the loop inside SyncToDatabase you could use Task Cancellation Pattern. Is SyncToDatabase itself an async method? I presume so, because there's an await in the while loop. Given that, the code which calls it could look something like this:

if(_syncTask != null && !_syncTask.IsCompleted)
{
    _ct.Cancel();
    // here you may want to make sure that the pending task has been fully shut down, 
    // keeping possible re-entrancy in mind
    // See: https://stackoverflow.com/questions/18999827/a-pattern-for-self-cancelling-and-restarting-task
    _syncTask = null;
}
_ct = new CancellationTokenSource();
// _syncTask = SyncToDatabase(ct.Token); // do not await
// edited to run on another thread, as requested by the OP
var _syncTask = Task.Run(async () => await SyncToDatabase(ct.Token), ct.Token);
_syncThreadStarted = true;

And SyncToDatabase could look like:

async Task SyncToDatabase(CancellationToken token)
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        DatabaseSyncer dbSyncer = new DatabaseSyncer();
        await dbSyncer.DeserializeAndUpdate();
        await Task.Delay(10, token); // after elapsed time re-run above code
    }
}

Check this answer for more details on how to cancel and restart a task.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Thanks for your answer. So the first part of your answer is to register the task? It does not seem to stop the task from occurring while SyncToDatabase is running, unless I misunderstand. – andrewb Nov 10 '13 at 22:09
  • @andrewb, `_syncTask` and `_ct` are the fields of your class. You check them before starting a new task with `_syncTask = SyncToDatabase(ct.Token)` and cancel the old instance of the task first. Thus, only one instance of the task is active. – noseratio Nov 11 '13 at 01:33
  • Does your code create a separate thread? If possible I need SyncToDatabase to run in a separate, non-UI thread. – andrewb Nov 11 '13 at 11:24
  • @andrewb, are you sure you need another thread here? See my notes about CPU and IO bound tasks. Anyway, I've updated the code to run it on another thread. Make sure you understand how nested tasks work: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx – noseratio Nov 11 '13 at 11:52
  • I need the UI thread to remain responsive. Please see my original post which I updated to explain more clearly what I need. – andrewb Nov 11 '13 at 22:05
  • @andrewb, I expect the UI would be responsive without an extra thread. If you only use `await dbSyncer.DeserializeAndUpdate();` and `await Task.Delay` inside `SyncToDatabase`, both asynchrnous, why would `SyncToDatabase` block the UI thread? Change 10ms to something more reasonable, like 250ms, and you should be fine. – noseratio Nov 11 '13 at 22:18
  • Where would I use Task.Delay? How does this solve the thread being entered twice? (Meaning a possible number of DeserializeandUpdates() calls issued) – andrewb Nov 11 '13 at 22:30
  • @andrewb, I can only see what you showed in you code fragment, there's call `Task.Delay` after `DeserializeAndUpdate` call. I've tried to explain how to cancel the previous pending task, so it doesn't get created twice. Sorry if I am not clear, but this is as far as I can help with your question. – noseratio Nov 11 '13 at 23:28
0

I may have misunderstood the question, but the execution of SynchToDatabase() will wait on the completion of await dbSyncer.DeserializeAndUpdaet() (due to the await keyword, go figure ;)) before executing the continuation, which will then delay for 10 ms (do you want 10ms or did you mean 10 seconds? Parameter for Task.Delay is in milliseconds), then loop back to re-execute the DbSyncer method, so I don't see the problem.

Moho
  • 15,457
  • 1
  • 30
  • 31