Click here to Skip to main content
15,936,709 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
I want to make a timer app where various timers are started asynchronously and then update the ui with each tick. (I'm doing this to learn the TPL library). But for some reason my code seems to hang.

I have three layers. The first is my WPF program, when I click on a button, I can create a new timer which is added to the panel

C#
private void AddStopwatch_OnClick(object sender, RoutedEventArgs e)
{
    var labelInput = new InputBox();

    labelInput.ShowDialog();
    if (labelInput.Result != MessageBoxResult.OK) return;

    var timer = new DisplayTimer(labelInput.Text);
    timer.Stopwatch = new AsyncStopwatchTimer(new Progress<TimeSpan>(async t =>
    {
        timer.Time = t;
        await Task.Delay(1000);
    }));
    _timers.Add(timer.Stopwatch);
    Panel.Children.Add(timer);
}


In the timer I have a button that starts and stops my timer. I used a RelativeSource Self to bind the Time dependency property to a label, so I just update the Time prop and that updates my label.

C#
private void StartStop_OnClick(object sender, RoutedEventArgs e)
{
    if (Stopwatch.IsStarted)
    {
        Stopwatch.Stop();
        Time = Stopwatch.GetDuration();
        StartStop.Content = "Start";
    }
    else
    {
        Stopwatch.Start();
        StartStop.Content = "Stop";
    }
}


And lastly, I have my timer start function that runs my progress update in a loop so it keeps updating the UI (or that's what I hope). If you noticed I put the delay in the progress, so the program is fully capable of telling how many times the UI should update.

I also have a stop function that should cancel the task that was started in the Start() method so the task doesn't keep looping endlesly.

C#
public async void Start()
{
    if (IsStarted) return;

    _cts = new CancellationTokenSource();
    _start = SystemClock.Instance.Now;
    IsStarted = true;
    await Task.Factory.StartNew(() =>
    {
        while (true)
        {
            if (_progress != null)
                _progress.Report(GetDuration());
            if (_cts.Token.IsCancellationRequested)
                return;
        }
    }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.FromCurrentSynchronizationContext());
}

public void Stop()
{
    if (!IsStarted) return;

    Instant end = SystemClock.Instance.Now;
    _intervals.Add(Duration.FromTicks(end.Ticks - _start.Ticks));
    IsStarted = false;
    _cts.Cancel();
}


Everything works fine until I press the "Start" button. Then the program just freezes up. Any ideas why? If you need more info, just ask and ye shall receive.

UPDATE : I found out that the code in AddStopwatch_OnClick keeps blocking the thread.
C#
{
    timer.Time = t;
    await Task.Delay(1000);
}


This should be run async, but it's being run on the UI thread, thus blocking the whole UI.
Posted
Updated 10-Oct-14 12:11pm
v3
Comments
Sergey Alexandrovich Kryukov 8-Aug-13 11:47am    
The expression "Everything works fine until I press the Start button" should become a part of some golden standard of the patterns of developer's thinking! :-)
—SA
Maciej Los 8-Aug-13 16:12pm    
;) laugh ;)
Sergey Alexandrovich Kryukov 8-Aug-13 16:29pm    
:-)
KenBonny 9-Aug-13 1:48am    
Hahaha, awesome. :p

Haven't used BackgroundWorker since .Net 2.0, but I guess it would have only improved. You might want to have a look at it.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx[^]

Look at this article as well:

http://stuff.seans.com/2009/05/21/net-basics-do-work-in-background-thread-to-keep-gui-responsive/[^]
 
Share this answer
 
Comments
KenBonny 9-Aug-13 3:30am    
Thanks for the useful info, but I was wondering if I could solve this using the TPL (Task Parallel Library).
[no name] 10-Aug-13 5:33am    
A good way of developing asynchronous apps is to use a console app as a test bed for your code and use printf()s to display which part of the code is executing. This is how I would tackle this problem.
KenBonny 12-Aug-13 1:45am    
VS2012 has great support for debugging multi threaded apps. I found out that the code in AddStopwatch_OnClick keeps blocking the thread.
{
timer.Time = t;
await Task.Delay(1000);
}

This should be run async, but it's being run on the UI thread, thus blocking the whole UI. I'm looking into fixing this now.
I found the solution. (Took me a while too.)

What I did wrong, was awaiting the Task.Factory.StartNew, making the thread wait for something that would never stop and thus hang the application. The solution in code:

C#
public void Start()
{
    if (IsStarted) return;

    _start = SystemClock.Instance.Now;
    IsStarted = true;
    if (_progress != null)
        Task.Factory.StartNew(async () =>
        {
            while (IsStarted)
            {
                await Task.Delay(_elapse);
                _progress.Report(GetDuration());
            }
        });
}     });
}

/// <summary>
/// Stop the stopwatch.
/// </summary>
public void Stop()
{
    if (!IsStarted) return;

    Instant end = SystemClock.Instance.Now;
    _intervals.Add(Duration.FromTicks(end.Ticks - _start.Ticks));
    IsStarted = false;
}


On the Start(), I start a Task without waiting for it's return, thus making it a background task. The async keyword does some magic behind the scenes to avoid locks. In each iteration, I wait for the specified time and then report progress to the UI with the _progress variable. _progress is an instance of Progress<t>[^] class, specifically designed to report progress in tasks.

Hope this helps people in the future when they want to report progress to the UI. :)
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900