Bug 16548 - async / await "finishes" running thread and resumes execution on main UI thread
Summary: async / await "finishes" running thread and resumes execution on main UI thread
Status: RESOLVED FIXED
Alias: None
Product: Class Libraries
Classification: Mono
Component: mscorlib ()
Version: master
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Marek Safar
URL:
Depends on:
Blocks:
 
Reported: 2013-12-02 16:46 UTC by Joshua Barker
Modified: 2013-12-13 11:53 UTC (History)
4 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Example demonstrating the async / await threading bug (12.54 KB, application/zip)
2013-12-10 17:51 UTC, Joshua Barker
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED FIXED

Description Joshua Barker 2013-12-02 16:46:30 UTC
When calling await within the body of an async method, the thread on which the task is running is "finished" and when the task is later resumed, all further logic executes on the main UI thread.

Example:
--------
    public class HighPerformanceTimer
    {
        Task _task;
        Task<Task> _wrapper;
        PauseTokenSource _pauseSource;
        CancellationTokenSource _cancelSource;

        public void Start()
        {
            var timerLogic = new Func<Task>(ExecuteAsync);
            _cancelSource = new CancellationTokenSource();
            _wrapper = Task.Factory.StartNew(timerLogic, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            _task = _wrapper.Unwrap();
           //AT THIS POINT, APPLICATION OUTPUT WINDOW EMITS: "Thread Started:  #3"
        }

        async Task ExecuteAsync()
        {
            while (!_cancelSource.IsCancellationRequested)
            {
                if (_pauseSource != null && _pauseSource.IsPaused)
                {
                    //AT THIS POINT, THE CURRENT MANAGED THREAD ID == #5...
                    Console.Write("Thread {0} - ", Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine("HPT - Waiting for application to un-pause to continue execution...");

                    //AT THIS POINT, APPLICATION OUTPUT WINDOW EMITS "Thread finished:  #3"
                    await _pauseSource.Token.WaitWhilePausedAsync();

                    //FROM THIS POINT FORWARD, ALL LOGIC EXECUTES ON MAIN UI THREAD (CURRENT MANAGED THREAD ID == #1)
                    Console.Write("Thread {0} - ", Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine("HPT - Application un-paused... Continuing execution...");
                }

                //TIMER LOGIC...
            }
        }
    }


PauseTokenSource.cs
-------------------

    //bssed on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx, by Stephen Toub
    public class PauseTokenSource
    {
        volatile TaskCompletionSource<bool> _tcs;

        internal static readonly Task CompletedTask = Task.FromResult(true);
        internal Task WaitWhilePausedAsync()
        {
            return _tcs == null ? CompletedTask : _tcs.Task;
        }

        public PauseToken Token { get { return new PauseToken(this); } }

        public bool IsPaused
        {
            get { return _tcs != null; }
            set
            {
                if (value) //pausing...
                {
                    Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), null);
                }
                else //un-pausing...
                {
                    while (true)
                    {
                        var tcs = _tcs;
                        if (tcs == null) { return; }
                        if (Interlocked.CompareExchange(ref _tcs, null, tcs) == tcs)
                        {
                            tcs.SetResult(true);
                            break;
                        }
                    }
                }
            }
        }

        public PauseTokenSource()
        {
            IsPaused = true;
        }
    }

    public struct PauseToken
    {
        readonly PauseTokenSource _source;

        public bool IsPaused
        {
            get { return _source != null && _source.IsPaused; }
        }

        public Task WaitWhilePausedAsync()
        {
            return IsPaused ? _source.WaitWhilePausedAsync() : PauseTokenSource.CompletedTask;
        }

        internal PauseToken(PauseTokenSource source)
        {
            _source = source;
        }
    }
Comment 1 Øystein Krog 2013-12-04 09:17:14 UTC
I just created an issue for a problem I'm seeing, where continuations are not run on the correct TaskScheduler.
Do you think this issue might be related ?

https://bugzilla.xamarin.com/show_bug.cgi?id=16587
Comment 2 Joshua Barker 2013-12-04 11:56:09 UTC
Yes, I think they are the same issue...
Comment 3 Rolf Bjarne Kvinge [MSFT] 2013-12-05 18:18:27 UTC
Marek, can you have a look at this?
Comment 4 Marek Safar 2013-12-10 12:36:49 UTC
I cannot really reproduce anything suspicious. Could you create valid self contained sample which exhibits the issue. I am trying to guess how to call your code and it still looks like you never sets _pauseSource.
Comment 5 Joshua Barker 2013-12-10 17:51:47 UTC
Created attachment 5633 [details]
Example demonstrating the async / await threading bug

This attachment is a basic example that demonstrates the described bug.

It contains a single button that plays/pauses the underlying timer loop, which is as a LongRunning Task.

At a set interval, the timer triggers a pulse event, which is handled by the main view controller.

On each loop / pulse, data is written to the output window indicating the action and thread ID.

When the timer is first started, there are 3 threads. Thread #1 is the main UI thread.  The second thread is for the long running task, and the third thread is for the pulse event.

When the timer is paused (via the await call to the pause token), the second thread gets "finished", even though the SynchronizationContext should keep it alive.

Because of this, when the timer is un-paused, all subsequent timer activity executes under the main UI thread (#1).
Comment 6 Joshua Barker 2013-12-10 17:53:56 UTC
Also, please take a look at the related bug to see if this is the same underlying issue or if they are different.
Comment 7 Marek Safar 2013-12-12 04:51:20 UTC
I am not seeing this behaviour with master version. There is only

AsyncAwaitThreadingBug[60394:80b] Thread 1 - HPT - Starting timer with 20.83ms interval

and then continuous

AsyncAwaitThreadingBug[60394:5b03] Thread 6 - AsyncAwaitThreadingBugViewController - Handling Pulse
AsyncAwaitThreadingBug[60394:4f03] Thread 4 - HPT - Time Elapsed / Remaining: 12322 / 11.33398
Comment 8 Joshua Barker 2013-12-12 19:15:18 UTC
Hi Marek,

Are you pausing and then un-pausing?  The bug only occurs after pausing/un-pausing.  Also, what version is "master".  I'm on version 4.2.2 (see below).

=== Xamarin Studio ===

Version 4.2.2 (build 2)
Installation UUID: 649009c5-4a73-427d-a239-f3bf28419fec
Runtime:
	Mono 3.2.5 ((no/964e8f0)
	GTK+ 2.24.20 theme: Raleigh
	GTK# (2.12.0.0)
	Package version: 302050000

=== Xamarin.Android ===

Version: 4.10.1 (Starter Edition)
Android SDK: /Users/Morkoth/Library/Developer/Xamarin/android-sdk-mac_x86
	Supported Android versions:
		2.1   (API level 7)
		2.2   (API level 8)
		2.3   (API level 10)
		3.1   (API level 12)
		4.0   (API level 14)
		4.0.3 (API level 15)
Java SDK: /usr
java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-462-11M4609)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-462, mixed mode)

=== Apple Developer Tools ===

Xcode 5.0.2 (3335.32)
Build 5A3005

=== Xamarin.iOS ===

Version: 7.0.4.209 (Indie Edition)
Hash: 23a0827
Branch: 
Build date: 2013-11-11 16:04:00-0500

=== Xamarin.Mac ===

Xamarin.Mac: Not Installed

=== Build Information ===

Release ID: 402020002
Git revision: c5f82958ae7d9af652b44b87ceff777b3ad19b91
Build date: 2013-11-19 15:35:40+0000
Xamarin addins: a4044fee09138f6fd031a9944b7caaeb51e57e80

=== Operating System ===

Mac OS X 10.9.0
Darwin joshuabkersimac 13.0.0 Darwin Kernel Version 13.0.0
    Thu Sep 19 22:22:27 PDT 2013
    root:xnu-2422.1.72~6/RELEASE_X86_64 x86_64
Comment 9 Marek Safar 2013-12-13 11:53:31 UTC
Fixed in master (it didn't make it to Xamarin.iOS 7.0.6)