2

I've coded a simplified version of my problem below. So I've got this service and in the method DoSomething - I want to do a synchronous check ( IsTrue() method ) followed by an async call ( database lookup ).

I only want to do the database lookup (EF6 async select - simulated by this.SuperSlowThing ) if the syncResult is false. This can be done by using && which will only continue to asyncResult in case syncResult is true.

My question: is this a good solution for this problem? If so, is there an easier way to do this?

internal class MyService
{
    public void DoSomething(bool someValue)
    {
        var syncResult = this.IsTrue(someValue);
        var asyncResult = new Lazy<Task<string>>(this.SuperSlowThing);

        if (syncResult && asyncResult.Value.Result.Length > 0)
        {
            Console.WriteLine("Did SuperSlowThing - result is " + asyncResult.Value.Result);
        }
        else
        {
            Console.WriteLine("Didn't do SuperSlowThing");
        }            
    }

    private bool IsTrue(bool someValue)
    {
        return someValue;
    }

    private Task<string> SuperSlowThing()
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        while (sw.ElapsedMilliseconds < 5000)
        {
            Thread.Sleep(500);
            Console.WriteLine("Delaying");
        }

        return Task.FromResult("Done!");
    }
}

EDIT

So let me elaborate a bit on what the code does in real life. The DoSomething method is really a database lookup. If the someValue is some number that is present in the web.config - then do a database lookup of the number in table A. This is the synchronous operation.

If a result is then found in table A, return that, otherwise lookup the number in table B.

If the number of someValue was not found in web.config - do the lookup in table B immediately.

Alex Celeste
  • 12,824
  • 10
  • 46
  • 89
Jochen van Wylick
  • 5,303
  • 4
  • 42
  • 64

5 Answers5

4

I see no real reason to use Lazy here. You can just check the syncResult first and then the asyncResult:

public async Task DoSomething(bool someValue)
{
    if (IsTrue(someValue) && await SuperSlowThing().Length > 0)
    {
       // ..
    }
    else
    {
       // ..
    }            
}

Or an even better, more readable version:

public async Task DoSomething(bool someValue)
{
    bool condition = false;

    if (IsTrue(someValue))
    {
        var result = await SuperSlowThing();
        if (result.Length > 0)
        {
            Console.Writeline(result);
        }
        condition = true;
    }

    if (!condition)
    {
        // ..
    }
}

Lazy is useful in multi-threaded cases, or where you're not completely sure if/when you'll use it or not. In your case you do know whether you use it in the scope of that method, so using Lazy only complicates things.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • As soon as you write this.SuperSlowThing() that task starts executing in the background. This is not what I want. I want it to execute ONLY in case syncResult is true. – Jochen van Wylick Aug 25 '14 at 14:29
  • @spike I assume you're referring to an older version of this answer, right? – i3arnon Aug 25 '14 at 14:31
  • Yes I think I was - damn you're quick! But in this setup - I don't have access to the result of that SuperSlowThing() call and will have to do it again. Or am I wrong? – Jochen van Wylick Aug 25 '14 at 14:37
  • @spike You are not wrong, but I added another (better) version to help you do that. It also looks better. – i3arnon Aug 25 '14 at 15:00
  • Thanks - yes so this is the nested IF approach. I'm trying to prevent that. Assume that I also have SuperSlowThing2() and 3, and that there's a situation where I need them all to not to be null + want to defer execution. Then I'll get this arrow shaped code which isn't so nice. – Jochen van Wylick Aug 26 '14 at 07:39
  • @spike that's actually not the reason for the multiple if statements. The reason is the you want to use the result both in the condition and inside the scope itself. You can do that with an "arrow shaped code", variable assignment like usr suggested, or `Lazy` like you suggested. I think the first option is more readable and understandable than the rest to most developers, but others are also viable. – i3arnon Aug 26 '14 at 09:21
1

Yes, a Lazy is appropriate for such cases. Your sample code is fine in principle. It has a problem in that it uses .Result instead of await. But that is just a detail immaterial to this discussion.

If you want to avoid using Lazy here, let me suggest an alternative:

string slowResult;
if (IsTrue(someValue) && (slowResult = await SuperSlowThing()).Length > 0)
{
   Console.WriteLine(slowResult);
}

Notice, that you can access slowResult multiple times without re-executing SuperSlowThing. This is what you were using Lazy for.

C# 6 will even let you declare a variable inline:

if (IsTrue(someValue) && (var slowResult = await SuperSlowThing()).Length > 0)

(At least that's how I think it's going to be.)

If you find the nested assignment confusing (which is understandable):

if (IsTrue(someValue))
{
   string slowResult = await SuperSlowThing();
   if (slowResult.Length > 0)
     Console.WriteLine(slowResult);
}

Thanks to await this problem is really the same that you would have with synchronous code. This is not about async or Task. Whether you write await SuperSlowThingAsync() or SuperSlowThing makes no difference to the logical flow of the method. Disregard asynchrony in your thinking.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    NICE - I didn't know this syntax where I can assign and use the value in an IF statement at the same time. This is exactly what I was looking for. The nested IFs is what I'm trying to avoid, I'm not a fan of that. – Jochen van Wylick Aug 25 '14 at 14:42
  • 1
    @spike I understand why you don't want to nest IF statements, but think about someone coming in to maintain your code later. What is clearer, for there to be 2 IF statements, or for you to use a (IMO) complicated assignment inside an if statement? – Kyle W Aug 25 '14 at 14:50
  • 1
    @spike C# 6 will even let you declare a variable inline. Maybe you like that as well. See the edit. – usr Aug 25 '14 at 15:23
  • @usr You can try out Roslyn stuff at [dotnetfiddle](https://dotnetfiddle.net/XaHZxI) - and yes, your C#6 suggestion seems to work. – default Aug 25 '14 at 15:33
  • @KyleW I agree that the assignment isn't that clear. So logically I have to inputs that can have two values (syncResult, superslow and true/false and null/not null). That makes 4 possible options. In my 2x2 decision matrix there's only 1 box for which I need the logic to do something different (lookup in table B). I wanted to shape my code that the condition for hitting this one box was as clear as possible ( IF(syncResult & slowThing != null) ) BUT without having to evaluate slowThing beforehand. And now it's just 2, but consider 5 lookups, the will start to look like an arrow. – Jochen van Wylick Aug 26 '14 at 07:31
  • @usr - could you point me to a resource on your comment: 'It has a problem in that it uses .Result instead of await. But that is just a detail immaterial to this discussion.' – Jochen van Wylick Aug 26 '14 at 13:11
  • @spike Find out what async and await do. Your async method is not async and calling Result removes the benefits of calling an async method (assuming it was actually async). Starting a task and immediately waiting for it rarely makes sense. – usr Aug 26 '14 at 13:14
0

If so, is there an easier way to do this?

What exactly does your solution achieve, that a simple if-construct would not?

var syncResult = this.IsTrue(someValue);

if (syncResult)
{
    var result = this.SuperSlowThing();
    Console.WriteLine("Did SuperSlowThing - result is " + result);
}
else
{
    Console.WriteLine("Didn't do SuperSlowThing");
}  
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • In this re-write you would need another if(result.Length > 0) ... etc. within that if(syncResult) - and that's exactly what I don't find very elegant and what I'm trying to prevent. – Jochen van Wylick Aug 25 '14 at 14:31
  • 1
    Well, the way you put it, the negative message is simply wrong. If SuperSlowThing has length 0, you tell people that it wasn't called. Personally, I'd just add another if. Simplicity is a good thing. – nvoigt Aug 25 '14 at 14:48
  • Yeah sorry - that's where my simplification doesn't really match the situation. The thing is - IF the result of superSlowThing is not null - that is returned, otherwise another database lookup is done. Sure nested IFs is a solution, but it's also what I'm trying to avoid here. See my other comments. Thanks for your response though! – Jochen van Wylick Aug 26 '14 at 07:43
  • Depending on your code, you replace your nested `if`s with non-nested `if`s that call return directly. – nvoigt Aug 26 '14 at 07:50
  • Yes I could, although then the 'multiple exit points within one method is not allowed' - police will come and hunt me down. – Jochen van Wylick Aug 26 '14 at 08:02
  • Tell them they belong to the 70s. Multiple exit points are perfectly fine as long as your method can be seen without scrolling. If it needs scrolling, there is something else wrong with it. – nvoigt Aug 26 '14 at 08:32
  • Nice. Funny you should mention that, the guy has gray hair so I'll tell him :). – Jochen van Wylick Aug 26 '14 at 09:10
0

If your goal is to encapsulate an on-demand, asynchronously retrieved value, then a Lazy<Task<T>> is an appropriate abstraction. It's hard to tell from your question if that's what you really want though.

Consider these two:

var x = new Lazy<int>(
    () => Task.Run(
        () => ComputeValue()));
var y = Task.Run(
    () => ComputeValue());

where ComputeValue is some expensive function returning an int.

Then the value assigned to y will begin computing immediately. Everybody who waits on y is waiting on an already started Task.

The value assigned to x will only begin computing the first time somebody asks for the value, but everyone will still wait on the same computation once it is started.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • I added some more explanation to the question. I also noticed that my simplification doesn't cover the whole case I'm dealing with. Furthermore, that's also what my understanding of lazy is and that's why I'm about to implement that solution now. – Jochen van Wylick Aug 25 '14 at 14:40
0

Lazy has nothing to do with sync or async, it has to do with deferred execution. In your case, you're not deferring the execution at all. You're creating the Lazy parameter, and then accessing it right away (assuming your value is true). Just putting

if(condition)
{
  var = await SuperSlowThing()
}

will give you the desired functionality.

Kyle W
  • 3,702
  • 20
  • 32
  • Thanks for your comment. The thing is I want to do something with the result of the SuperSlowThing() ( use it in an IF ) and I want to avoid nesting a bunch of IFs. – Jochen van Wylick Aug 25 '14 at 14:52
  • @spike Yes I'm aware, see my response to your comment about that, as if I was in charge of someone and they wrote the code that you want to write, I would have a conversation with them about readability and maintainability and what's important. – Kyle W Aug 25 '14 at 15:00