none
Rx Linq Query Locking UI

    Question

  • I have the code below as an example.  I have 2 buttons... 1st) to call the tasks and 2nd) to send a string to the subscriber. 

    #1 task delays for a second and returns a value. ( This Works )

    #2 task waits for a string before continuing. Clicking the 2nd button will allow it to continue.  ( This Works )

    #3 task tries to wait for a string similar to #2, but using the approach in #1 ( i.e. Task.Run... ).  ( This locks up the UI ). 

    While I would like to just use #2 task ( which works in most cases for me ),  I have a scenario where I must have a Method that can not be async/await and need to create a task similar to #3 to get the string and return it via one of the out parameters. 

    Meaning, I will have a Method like so...

    private bool DoSomething(out string value1, out string value2) { //... }

    Does anyone see why this error occurs?  Any help is appreciated.

    private static readonly Subject<string> _myStringTest = new Subject<string>();
    public static IObservable<string> StringTest { get { return _myStringTest.AsObservable(); } }
    
    private async void btnDoTasks_Click(object sender, EventArgs e)
    {
        string stringValue = "";
        // #1
        stringValue = Task.Run(async () => await DoTask1()).Result;
        // #2
        stringValue = await DoTask2();
        // #3
        stringValue = Task.Run(async () => await DoTask2()).Result;
        tbxResults.Text = stringValue;
    }
    
    private async Task<string> DoTask1()
    {
        await Task.Delay(1000);
        return ("Task 1 Complete.");
    }
    
    private async Task<string> DoTask2()
    {
        string value;
        value = await (from st in StringTest
                       select st).FirstOrDefaultAsync();
        return (value);
    }
    
    private void btnSendString_Click(object sender, EventArgs e)
    {
        _myStringTest.OnNext("The button sent this message.");
    }

    Sunday, August 11, 2013 6:56 PM

Answers

  • Hi,

    > No, I'm not using Result. [snip]

    The code in the API is fine, perhaps, though it's not what I was referring to when I asked about your use of Task.Result.

    To be clear, the problem is that somewhere else in your application you're blocking the UI thread.  The example that you provided uses Task.Result, which is why you were able to repro.

    As I've shown, simply replacing .Result with await avoids deadlock, even if the API uses FirstOrDefaultAsync; i.e., your particular example's deadlock has nothing to do with that Rx operator.

    > Since the Task.Delay example worked, I was assuming calling in the same manner with my API would return similar results.

    But your Task.Delay example didn't work!  Maybe this is the source of your confusion.

    It only appeared to work because it doesn't require any further UI interaction.  The UI is actually freezing for 1 second.  Try setting the delay to 10 seconds and you'll see what I mean – the UI is frozen for 10 seconds.  The reason is simply because you're using Task.Result instead of await.

    The difference between your first and third examples is that your third example blocks the UI thread until it receives a notification on the UI thread.  In other words, it permanently deadlocks.

    > I put in a request to the vendor to see if they support the new async features.

    It's a good idea to ask the vendor for an async-capable API; however, if they don't provide one, then I'd strongly recommend not using Task.Run.  Instead, consider calling their API synchronously – as long as it doesn't block the UI thread for longer than approx. one third of a second, or whatever delay is acceptable for your particular application.

    Regardless, certainly do not call Result or Wait on the UI thread; otherwise, there's a good chance that it will deadlock.

    Note that this problem isn't specific to Rx.

    - Dave


    http://davesexton.com/blog

    • Marked as answer by shaggygi Tuesday, August 13, 2013 12:42 PM
    Monday, August 12, 2013 1:17 PM

All replies

  • Hi,

    1. Don't use Task.Result.  You're blocking the UI thread.  Instead, simply await the task.

    For example:

    stringValue = await Task.Run(async () => await DoTask2());

    That's enough to unblock your example; however, there are additional things that you should consider when writing async code...

    2. If the source is naturally asynchronous, then model it properly.

    If your real method (DoSomething(out string value1, out string value2)) does asynchronous things, such as subscribing to an Rx query or calling async methods such as FirstOrDefaultAsync, and if you can change its signature, then change it to return Task<bool> or IObservable<bool> instead of simply returning bool(Edit: See #5 below about out and ref parameters.)

    3. Don't introduce concurrency unnecessarily.

    Try to think reactively when designing your APIs around observables and tasks.

    Don't introduce concurrency from the UI thread, like background workers, unless you absolutely must; e.g., if DoSomething is a long-running or resource-intensive operation, and it's not naturally asynchronous, then you may want to consider using Task.Run; however, if it only blocks the UI thread for about a third of a second, then just invoke it synchronously.

    4. Don't use extra Tasks or awaits when they're unnecessary.

    For example, your code could be simplified to:  (Edit: Assuming your example represents a real need to introduce concurrency in your actual app.)

    stringValue = await Task.Run(() => DoTask2());

    And considering my suggestions above to think reactively and to model asynchrony at the source, your example can be simplified even further:

    stringValue = await StringTest.Take(1);

    5. Try to avoid out and ref parameters when working with async code.  Consider returning Tuple or a custom type instead.

    For example:

    private async Task<Tuple<bool, string, string>> DoTask2()
    {
    	var value = await StringTest.Take(1);
    
    	return Tuple.Create(true, value, "Value #2");
    }

    - Dave


    http://davesexton.com/blog

    • Edited by Dave Sexton Sunday, August 11, 2013 8:09 PM Clarification
    Sunday, August 11, 2013 8:02 PM
  • Hi Dave,

    Thanks a bunch for your detailed reply.  The example I provided was a very basic example and tried to simplify to perform what I am seeing with another API I'm working on.  The API also has a Rx Linq query deep within it where it is processing data in/out of a socket and also locks up when getting to a point using a Rx Linq query.

    The API was originally developed around an Async model and uses Rx a good bit.  The majority of the time I would be using Async.  However, I just came across this error as a friend needed to call in the manner I mentioned above.

    Also, I don't really need the out arguments, but it appears I do need to be able to call and return with only a string without having an async signature.  Meaning, it would be something like this...

    private string DoSomething() { //... }

    So I thought #3 would work similar to how #1 worked in my original example.  It seems like you could call DoSomething() that included a Task within that runs async and then return the results from the task back to what called DoSomething.  Maybe, I'm missing some logic here.

    I'll keep reviewing this and also look into the options of being able to call the Method async instead of how my friend came across this issue.

    Thanks again.

    Sunday, August 11, 2013 10:08 PM
  • Hi,

    >  The example I provided was a very basic example and tried to simplify to perform what I am seeing with another API I'm working on.

    Are you using Task.Result in your real API also?  If not, then can you provide a better example of what you're trying to do?

    If the UI is deadlocking it's probably because you're explicitly using some kind of blocking operation; e.g., Result, Wait, etc.  Just don't do that.

    > The API also has a Rx Linq query deep within it where it is processing data in/out of a socket and also locks up

    How is it being scheduled?  Are you passing in DispatcherScheduler or using ObserveOnDispatcher?  This could of course cause a deadlock if you're also using a blocking operation on the UI thread; e.g., Result or Wait.

    > I do need to be able to call and return with only a string without having an async signature.

    That seems like a strange requirement for code that's doing asynchronous things (I assume).  Would you care to elaborate as to why you can't make your method return Task<string> instead?

    > So I thought #3 would work similar to how #1 worked in my original example.

    In example #3, there's really no point to using Task.Run.  That's what I tried to explain before.  You're introducing concurrency into a method that awaits asynchronously for a notification.  The extra concurrency is simply unnecessary.

    Perhaps you could provide a better example showing why you actually think you need to call Task.Run.

    - Dave


    http://davesexton.com/blog

    Monday, August 12, 2013 2:04 AM
  • No, I'm not using Result.  The code looks like this with a Timeout.  This is the part that uses the Rx Linq part within the async calls.  This is a little more complicated since it is processed within several Methods and why I used the original example... and to make sure I could duplicate the issue.

    result = await (from v in values
                    select v).FirstOrDefaultAsync().Timeout(5000));

    The tool my friend is using appears to allow synchronous call methods ( at least in the samples provided ).  As a result, I was trying to do a test by calling in the original example.  I put in a request to the vendor to see if they support the new async features.  I hope they do:)

    I would rather use the async signature Method as I know this works...

    private async Task<string> DoSomething() { //... }

    However, if the tool only allows synchronous calls it would be something like below while trying to call the API Method within...

    private string DoSomething() { //... }

    Since the Task.Delay example worked, I was assuming calling in the same manner with my API would return similar results.

    Hopefully I will hear some good news from the vendor.

    Thanks again.

    Monday, August 12, 2013 11:36 AM
  • Hi,

    > No, I'm not using Result. [snip]

    The code in the API is fine, perhaps, though it's not what I was referring to when I asked about your use of Task.Result.

    To be clear, the problem is that somewhere else in your application you're blocking the UI thread.  The example that you provided uses Task.Result, which is why you were able to repro.

    As I've shown, simply replacing .Result with await avoids deadlock, even if the API uses FirstOrDefaultAsync; i.e., your particular example's deadlock has nothing to do with that Rx operator.

    > Since the Task.Delay example worked, I was assuming calling in the same manner with my API would return similar results.

    But your Task.Delay example didn't work!  Maybe this is the source of your confusion.

    It only appeared to work because it doesn't require any further UI interaction.  The UI is actually freezing for 1 second.  Try setting the delay to 10 seconds and you'll see what I mean – the UI is frozen for 10 seconds.  The reason is simply because you're using Task.Result instead of await.

    The difference between your first and third examples is that your third example blocks the UI thread until it receives a notification on the UI thread.  In other words, it permanently deadlocks.

    > I put in a request to the vendor to see if they support the new async features.

    It's a good idea to ask the vendor for an async-capable API; however, if they don't provide one, then I'd strongly recommend not using Task.Run.  Instead, consider calling their API synchronously – as long as it doesn't block the UI thread for longer than approx. one third of a second, or whatever delay is acceptable for your particular application.

    Regardless, certainly do not call Result or Wait on the UI thread; otherwise, there's a good chance that it will deadlock.

    Note that this problem isn't specific to Rx.

    - Dave


    http://davesexton.com/blog

    • Marked as answer by shaggygi Tuesday, August 13, 2013 12:42 PM
    Monday, August 12, 2013 1:17 PM
  • Yes, I see your point now.  I contacted the vendor and they are going to help us with an async solution.  Thanks again for your time on this issue.

    Tuesday, August 13, 2013 12:43 PM