{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/reactive/issues/456","repository_url":"https://api.github.com/repos/dotnet/reactive","labels_url":"https://api.github.com/repos/dotnet/reactive/issues/456/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/reactive/issues/456/comments","events_url":"https://api.github.com/repos/dotnet/reactive/issues/456/events","html_url":"https://github.com/dotnet/reactive/issues/456","id":285736345,"number":456,"title":"ToTask should dispose of the subscription immediately on completion or error","user":{"login":"Lukazoid","id":1301620,"avatar_url":"https://avatars3.githubusercontent.com/u/1301620?v=4","gravatar_id":"","url":"https://api.github.com/users/Lukazoid","html_url":"https://github.com/Lukazoid","followers_url":"https://api.github.com/users/Lukazoid/followers","following_url":"https://api.github.com/users/Lukazoid/following{/other_user}","gists_url":"https://api.github.com/users/Lukazoid/gists{/gist_id}","starred_url":"https://api.github.com/users/Lukazoid/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lukazoid/subscriptions","organizations_url":"https://api.github.com/users/Lukazoid/orgs","repos_url":"https://api.github.com/users/Lukazoid/repos","events_url":"https://api.github.com/users/Lukazoid/events{/privacy}","received_events_url":"https://api.github.com/users/Lukazoid/received_events","type":"User","site_admin":false},"labels":[{"id":910861234,"url":"https://api.github.com/repos/dotnet/reactive/labels/on-hold-until-community-reforms-join-us-in-slack","name":"on-hold-until-community-reforms-join-us-in-slack","color":"e99695","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2018-01-03T16:23:36Z","updated_at":"2018-05-25T10:12:31Z","closed_at":null,"author_association":"NONE","body":"Currently [TaskObservableExtensions.ToTask<TResult>(IObservable<TResult>)](https://github.com/Reactive-Extensions/Rx.NET/blob/0a03db0bee3d80656accff16229a6cd152d82d18/Rx.NET/Source/src/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs#L360) completes or sets the exception on the resulting `Task` before disposing of the subscription, this can be seen [here](https://github.com/Reactive-Extensions/Rx.NET/blob/0a03db0bee3d80656accff16229a6cd152d82d18/Rx.NET/Source/src/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs#L317-L323) and [here](https://github.com/Reactive-Extensions/Rx.NET/blob/0a03db0bee3d80656accff16229a6cd152d82d18/Rx.NET/Source/src/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs#L325-L338).\r\n\r\nAs `TaskCompletionSource.TrySetResult` and `TrySetException` will often run the task continuations it may not unsubscribe until the next await, this could be disastrous when the original subscription uses any resources which the task continuation will need exclusive access to, for example:\r\n\r\n    async Task Main()\r\n    {\r\n        var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), \"ToTaskTest.txt\");\r\n    \r\n        var secondLine = await WhenLinesReadFromFile(filePath).Skip(1).FirstOrDefaultAsync().ToTask().ConfigureAwait(false);\r\n        if (secondLine == \"DELETE ME\")\r\n        {\r\n            File.Delete(filePath);\r\n        }\r\n    }\r\n    \r\n    IObservable<string> WhenLinesReadFromFile(string filePath)\r\n    {\r\n        return Observable.Create<string>(async (obs, ct) =>\r\n        {\r\n            var file = File.OpenText(filePath);\r\n            string line;\r\n            while ((line = await file.ReadLineAsync().ConfigureAwait(false)) != null)\r\n            {\r\n                ct.ThrowIfCancellationRequested();\r\n                \r\n                obs.OnNext(line);\r\n            }\r\n            \r\n            return file;\r\n        });\r\n    }\r\n\r\nAs `WhenLinesReadFromFile` only releases the file handle on unsubscribe the above code will attempt to delete the file while the handle is still open.\r\n\r\nI propose that the `_disposable.Dispose();` be moved before the `_tcs.TrySetResult` and `_tcs.TrySetException` calls, there's probably no harm in moving the `_ctr.Dispose()` earlier too (as we know there is already a result/exception). This would ensure that the continuation is only run once the subscription has been disposed.\r\n  \r\n  "},"comment":{"url":"https://api.github.com/repos/dotnet/reactive/issues/comments/392007855","html_url":"https://github.com/dotnet/reactive/issues/456#issuecomment-392007855","issue_url":"https://api.github.com/repos/dotnet/reactive/issues/456","id":392007855,"user":{"login":"akarnokd","id":1269832,"avatar_url":"https://avatars2.githubusercontent.com/u/1269832?v=4","gravatar_id":"","url":"https://api.github.com/users/akarnokd","html_url":"https://github.com/akarnokd","followers_url":"https://api.github.com/users/akarnokd/followers","following_url":"https://api.github.com/users/akarnokd/following{/other_user}","gists_url":"https://api.github.com/users/akarnokd/gists{/gist_id}","starred_url":"https://api.github.com/users/akarnokd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akarnokd/subscriptions","organizations_url":"https://api.github.com/users/akarnokd/orgs","repos_url":"https://api.github.com/users/akarnokd/repos","events_url":"https://api.github.com/users/akarnokd/events{/privacy}","received_events_url":"https://api.github.com/users/akarnokd/received_events","type":"User","site_admin":false},"created_at":"2018-05-25T10:12:31Z","updated_at":"2018-05-25T10:12:31Z","author_association":"CONTRIBUTOR","body":"For reference, [Reactive4.NET](https://github.com/akarnokd/Reactive4.NET/blob/master/Reactive4.NET/subscribers/TaskFirstSubscriber.cs#L68) disposes before setting the cancelled state."}}
{"actor":{"display_login":"akarnokd"}}