0

I am currently rewriting my code to add asynchronous execution. However, I'm having trouble with adapting a group of methods.

I looked at a few questions like this one before asking but often time the problem was using Task.Run() instead of async/await.

=======Abstract=======

My program will be used to automate a test bench with multiple valves. As such I have written methods like open/closeValve() that open or close a valve to a certain threshold, increase/decreaseFlow() to select which valve to open or close and a regulate() method that calls increqse/decreaseFlow().

It's just recently that I decided, after reading about it, that adding asynchronous execution was a good idea to display data about the bench in real time. But my methods were all thought out to work in a synchronous way so I'm having trouble adapting them.

=======Code=======

For example here is the openValve() method

public int openValve(int limit)
    {
        if(_masterFlowrate >= _instructions - 0.1 && _masterFlowrate <= _instructions + 0.1)
        {
            //flowrate ~= instructions => no need to manipulate the valves
            return 1;
        }
        else
        {
            if(valveOpening < limit)
            {
                //flowrate < instructions & valve opened less than the limit
                // => opening valve untill the limit or untill flowrate = instructions
                valveOpening++;
 
                //Opening the valve by writing the value in a command computer via Modbus
                master.WriteSingleRegister(1, _mdbAdr, Convert.ToUInt16(ouvertureValve));
                return 0;
            }
            else
            {
                //flowrate < instructions & valve already opened beyond the limit
                // => not manipulating the valve
                return -1;
            }
        }

To adapt this method to asynchronous execution I changed the returned value type from int to Task<int> and changed return 1 to return Task.FromResult(1) for example.

Now the increaseFlow() method that directly uses the previous one looks like this (Vp, Vm and Vg are instances of the Valve class)

 public int increaseFlow()
    {
        int retVal = 0;
        switch(openingStep)
        {
            case 1:
                retVal = Vp.openValve(60);
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 2;
                }
                break;

            case 2:
                retVal = Vg.openValve();
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 3;
                }
                break;

            case 3:
                retVal = Vm.openValve();
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 4;
                }
                break;
        }

        return retVal;
    }

This is where I'm starting to have troubles. I've tried changing int retVal = 0; into var retVal = Task.FromResult(0) which doesn't give me any compiler error.

Then adding awaits keywords before each openValve() but this gives me the following :

Error   CS0029  Cannot implicitly convert type 'int' to 'System.Threading.Tasks.Task<int>'

And then the regulate() method calls increaseFlow().

=======Conclusion=======

That's where I began to think that maybe the code wasn't thought out to work asynchronously and that it needed to be rearranged. Is this really my problem or is there a solution that wouldn't require to completely redo the methods. I'm not saying that I wouldn't do it but I'm interested in knowing if the structure is wrong or if I'm just not using the right keywords/methods etc...

6
  • Wait a second ... Is this going to control HARDWARE? Commented May 10, 2021 at 14:20
  • 3
    So far, I see nothing in that code that would benefit from being asynchronous. You're still doing synchronous execution, but wrapping return values in task objects. If you want trule async code, you need to make sure you have no blocking calls. That would probably involve changing things like WriteSingleRegister and openValve, so it's a bit more complicated. Commented May 10, 2021 at 14:26
  • @Fildor It's not going to control hardware directly, the command computer writes and reads registers in an OMNI Flow computer that is connected to hardware. Commented May 10, 2021 at 14:37
  • @EtiennedeMartel Would you advise that I stop trying to adapt my code ? Because I see a benefit, I wouldn't want the programm to be blocked while he should have been reading data like the flowrate and thus not adjust it. Now in my case I could potentially skip it as the valves in question are really slow to move. But I think this is a good opportunity to learn Commented May 10, 2021 at 14:42
  • @EtiennedeMartel also yes, that was what I thought at the beginning : my code isn't thought out to be executed asynchronously. So any tips to rearrange it would be welcomed Commented May 10, 2021 at 14:43

2 Answers 2

5

adding asynchronous execution was a good idea to display data about the bench in real time

That's not really what asynchronous execution is for. Asynchrony is all about freeing up threads, which on the client side translates into a responsive UI, and on the server side translates into a more scalable service.

Regarding "display data about the bench in real time", it sounds like what you really want is a form of progress reporting. (Note that "progress" in this context can be any collection of data, not just a percent-complete). The built-in IProgress<T> is made for progress reporting, and works perfectly well with synchronous code even though most of the examples are for asynchronous code.

You would want asynchronous code if your app primarily made I/O-bound requests. But the proper way to make something asynchronous is to start at the lowest levels, e.g., WriteSingleRegister, and then let the asynchrony grow up through the app from there. If you're controlling hardware, asynchrony is possible but likely not the best solution.

Sign up to request clarification or add additional context in comments.

2 Comments

So if I really wanted to rearrange my code anyway, would it be better to start rewriting the method that isn't called by any other (regulate()) or to start by the one that is called "first" (openValve()) ?
@FrougeurPro: Start with the lowest-level methods.
1

A possible reason is the difference between async Task<T> and Task<T>. I.e.

public async Task<int> MyMethod(){
    return 42; // Task creation is implicit
}

And

public Task<int> MyMethod(){
    return Task.FromResult(42); // Task creation is explicit
}

The much larger question is why you are rewriting your system with async methods. There are typically a few main reasons:

  1. You are using some kind of IO, disk, network etc. Asynchronous methods helps avoid the blocking the thread when using these methods.
  2. You are using something very compute intensive, and do not want to block the UI while the CPU is processing for several seconds.
  3. Wrapping a blocking IO call in a thread to avoid the blocking the UI. (when true Async IO api is not available).

Just changing methods to async and using Task.FromResult will probably not result in any advantage at all since all the code will still run on the main thread, and any cpu intensive operations or blocking calls will still freeze the UI.

If the goal is to avoid UI blocking then Task.Run is probably the way to go. But this will require your program to be threadsafe. You could use a limitedConcurrencyTaskScheduler to move work on a background thread, but still prevent multiple commands from being executed concurrently. This reduces the risk of thread safety problems.

Overall I would recommend re-analyzing what the actual problem is that you are trying to solve. Using asynchronous patterns might be part of such a solution, but it is probably not the only way.

3 Comments

That's a question I had while reading documentation, when using async/await do you still need to create threads manually or is it done automatically when using those keywords ?
@FrougeurPro No, async/await just invokes compiler magic to generate a statemachine that gives the appearance of synchronous execution. Task.Run or equivalent will schedule work to be executed on a threadpool thread. You normally never create threads manually since there is little downside with using the threadpool.
@FrougerPro Not sure how you intend to use the timer. It will not solve a blocked UI thread by itself. But you might use it to update the UI if a background thread is used to read the registers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.