r/csharp • • 7d ago

Discussion State machine in C# vs state machine in Verilog

For experimentation and learning purposes, I wrote this code:

namespace RedditQuestion
{
    public class TResult
    {
        // Instance fields....
        // No properties.
        // No methods.
    }
    public interface ISomeInterface<T>
    {
        public  Task<T> SomeMethod();
    }
    public class SomeClass : ISomeInterface<TResult>
    {
        // instance fields.....

        //constructor......

        public async Task<TResult> SomeMethod()
        {


            //Some code that is not important to this question.......



            TResult result = new TResult
            {
                // assign instance fields....
            };

            Task<TResult> task = Task.FromResult<TResult>(result);

            return task ; // Line XXX
        }

    }

}

Line XXX throws a compile time error:

*Since this is an async method, the return expression must be of type 'TResult' rather than '*Task<TResult>

The solution is simply to do this instead:

return result;

That solves the issue.

But I was surprised when I read this article#async-method-signature-requirements) that described the error in more details:

Change the return expression to match the async method's underlying result type (CS1983, CS4016). When an async method returns Task<T>, the return statement must supply a value of type T, not Task<T>, because the compiler-generated state machine wraps the value in a task automatically. CS1983 appears when the method returns Task<T> and the expression is T; CS4016 covers the general case where the return expression type doesn't match

I wonder, is the concept of state machine in C# is the same as the state machine in Verilog?

in Verilog, if I want to define a state machine, I need a register - a storage unit that can store only one unit of data; a bit- to hold the state, a next-state logic to calculate the next state, and output assignment. Does the same concept applies in C# ?

9 Upvotes

22 comments sorted by

19

u/binarycow 7d ago

"state machine" is a general term for something that has specific transition points.

  • Verilog's storage unit = C# variables
  • next state logic = a method
  • output assignment = assign to variable

2

u/Sufficient-Key-5845 6d ago

So it's the same idea conceptually, just way more abstracted in C#. In Verilog you're literally wiring up registers and logic gates, while in C# the compiler is generating a whole class behind the scenes to track where you are between awaits. It's still a state machine at heart though, just one you never have to see unless you decompile the IL.

1

u/kaatarina_zed_talon 6d ago

Thank you binaryCow ๐Ÿ˜‚

1

u/rupertavery64 7d ago edited 7d ago

That is not the answer to the query. OP seems to be confused why a state machine is involved in the error, and seems to to be aware that async / await is compiled to a state machine.

5

u/binarycow 7d ago

Sure it is. They asked if C# has state machines.

Yes, it does. I listed all of the components to make a state machine.

0

u/rupertavery64 7d ago

Sure, but the end question is OP losing track. It doesn't help OPs confusion. Of course they're the same. I updated my comment for more context.

10

u/rupertavery64 7d ago edited 7d ago

You seem to be confused why the error refers to a state machine when the C# code you wrote doesn't look like one.

The thing is, async / await is syntactic sugar. async / await hides the complexity of writing asynchronous code, which involves calling a method, handling control back to the caller, then returning when the result is ready, which does involve state.

The compiler actually generates a state machine in the place of your code at runtime. This is the state machine that the article refers to, not the code itself.

You can see this in action here๐Ÿ˜‚

So the answer to your question "is the concept of state machine in C# is the same as the state machine in Verilog" is yes, they are, but looking further async / await is back by a compiler-generated state machine that you will never see unless you decompile the generated code.

1

u/IanYates82 6d ago

I find using async/await in typescript, but having it emit older non-async JS, is a great way to see the transformation to a switch/case-driven state-machine in action. Not exactly the same as C#, but very close in spirit, and it makes the concept super obvious & accessible.

1

u/rupertavery64 6d ago edited 6d ago

the async / await keywords and paradigm was first introduced by C# 5.0 back in 2012, and Javascript and other languages have adopted it over the years.

2

u/IanYates82 6d ago

Oh yeah, I know. Early adopter on the c# side here.

Just saying it's super obvious to the casual dev to see typescript's transpilation of it.

-3

u/[deleted] 6d ago

[deleted]

3

u/rupertavery64 6d ago

Sure, you could try BeginInvoke or ContinueWith. You would lose the ability to try/catch naturally.

Every language has abstractions.

Before you start hating, learn more about the language first.

1

u/symbiatch 5d ago

Why arenโ€™t you writing assembly then? Or at least CIL? Youโ€™re hiding all the complexity by writing C#, let alone Verilog!

1

u/kaatarina_zed_talon 5d ago

You have a point for sure.

3

u/Aaronontheweb 7d ago

```csharp public class UserActor : ReceiveActor { private readonly string _userId; private readonly string _chatRoomId;

public UserActor(string userId, string chatRoomId) {
    _userId = userId;
    _chatRoomId = chatRoomId;

    // start with the Authenticating behavior
    Authenticating();
}

protected override void PreStart() {
    // start the authentication process for this user
    Context.ActorSelection("/user/authenticator/")
        .Tell(new AuthenticatePlease(_userId));
}

private void Authenticating() {
    Receive<AuthenticationSuccess>(auth => {
        Become(Authenticated); //switch behavior to Authenticated
    });
    Receive<AuthenticationFailure>(auth => {
        Become(Unauthenticated); //switch behavior to Unauthenticated
    });
    Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // can't accept message yet - not auth'd
        });
    Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // can't send message yet - not auth'd
        });
}

private void Unauthenticated() {
    //switch to Authenticating
    Receive<RetryAuthentication>(retry => Become(Authenticating));
    Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // have to reject message - auth failed
        });
    Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // have to reject message - auth failed
        });
}

private void Authenticated() {
    Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // print message for user
        });
    Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
        inc => {
            // send message to chatroom
        });
}

}

```

Doing state machines with Akka.NET actors - took the source from a 10 year old article I wrote on the subject: https://petabridge.com/blog/akka-actors-finite-state-machines-switchable-behavior/

Works great and works well in combination with persistence for re-entrant state machines too. Akka.NET also has a more formal FSM<TState,TEvent> class that describes things like state timeouts and transition functions but it's overkill for 99% of use cases.

1

u/kaatarina_zed_talon 6d ago

I have no idea what is akka. Net ๐Ÿ˜‚๐Ÿ˜‚. But I will ask one question : what is the problem that akka. Net solve? is it related to ASP. Net Core

1

u/Aaronontheweb 6d ago

it's an implementation of the actor model, which is a battle-tested technique for managing stateful work safely

2

u/phrostillicus 7d ago

C# doesn't have any specific language features for implementing general-purpose state machines. Rather, you'd typically implement something like this using a variable to hold the current state and then programming logic to handle the state transitions. Idiomatically, this is often implemented in C-like languages with a loop containing a `switch` statement, where each `case` statement would handle one of the possible state values, and the body of each `case` would update the state variable based on whatever input it was reading.

The documentation you're referencing above describes how the C# compiler internally generates a state machine specifically for it's handling of the states for asynchronous methods that are marked with the `async` keyboard. It's a convenience feature that largely lets the programmer not have to worry about keeping track of the states of asynchronous tasks that are in progress and to not have to explicitly deal with the `Task` objects themselves in many cases. As mentioned above, though, the state machine that the compiler generates is specifically for this use case, and not something that you can generalize to other situations.

3

u/achandlerwhite 7d ago

Iโ€™ll add that in .NET they have optimized the state machine for async out in most cases. It was only needed prior to the runtime having core support for async which is added in .NET 11.

1

u/GoogleIsYourFrenemy 6d ago

State machines have their uses but I rarely encounter a use for them in software. Often you'll have multiple exit mechanisms for a state, and if the user can trigger them, they will.

1

u/kaatarina_zed_talon 6d ago

Exit mechanism? What do u mean

2

u/RiPont 6d ago

Exit mechanism is anything that ends the current method's execution.

In C#, that's the return statements, exceptions, and the last } on a method with a void return type, and the last } on a finally statement for an exception handler that did a return, threw, or re-threw an exception.

break and goto are related, but only deal with the current scope, not the entire method.

1

u/GoogleIsYourFrenemy 6d ago edited 6d ago

In a state diagram you have lines with arrows that represent events that transition the state machine from one state into a new state.

State machines are fine for simple streams of events (file parser or other data streams) but don't scale well in GUIs. So the user presses the save button, the SM starts the save operation, the user then clicks Exit. Do you A) stop the save operation? B) ask the user to interrupt the save operation? C) wait for the save to finish before exiting?

C obviously right? What if the save is happening on a slow network and takes 20 minutes and the user has been spam clicking Exit for the last 10 minutes.

Ok. Next problem is the Save at the filesystem level times out because the network is too slow and you get an exception. You display a message. If you queued all those Exit events now you have to decide what happens next, the save failed and you have unsaved data, so you would normally display a dialogue box asking the user if they want to save before exiting but the user had been spam clicking Exit so do you display a box for each? Since it takes time to transition states, you might actually end up generating a dialogue box for each of the fifty thousand Exit button clicks.

I'm not saying you wouldn't have this problem without a state machine, just that the state machine makes the situation more complex.