Cysharp / MagicOnion

Unified Realtime/API framework for .NET platform and Unity.
MIT License
3.8k stars 423 forks source link

Client Results #783

Closed mayuki closed 3 months ago

mayuki commented 3 months ago

This PR adds a feature called "Client results," which allows the server to invoke client methods and receive results.

"Client results" is inspired by a feature of the same name implemented in SignalR. This enables invoking a particular client's method from the server's Hub or application logic and receiving results.

interface IMyHub : IStreamingHub<IMyHub, IMyHubReceiver>
{
}

interface IMyHubReveiver
{
    // The Client results method is defined in the Receiver with a return type of Task or Task<T>
    Task<string> HelloAsync(string name, int age);

    // Regular broadcast method
    void OnMessage(string message);
}

// Client implementation
class MyHubReceiver : IMyHubReceiver
{
    public async Task<string> HelloAsync(string name, int age)
    {
        Console.WriteLine($"Hello from {name} ({age})");
        var result = await ReadInputAsync();
        return result;
    }
    public void OnMessage()
    {
        Console.WriteLine($"OnMessage: {message}");
    }
}

On the server, method calls can be made through Client or Single of the group, and the result can be received.

var result = await Client.HelloAsync();
Console.WriteLine(result);
// or
var result2 = await _group.Single(clientId).HelloAsync();
Console.WriteLine(result2);

Exceptions

If an error occurs on the client, an RpcException is thrown to the caller, and if the connection is disconnected or a timeout occurs, a TaskCanceledException (OperationCanceledException) is thrown.

Timeout

The timeout for server-to-client calls is 5 seconds by default. If the timeout is exceeded, a TaskCanceledException (OperationCanceledException) is thrown to the caller. The default timeout can be set via the MagicOnionOptions.ClientResultsDefaultTimeout property.

To explicitly override the timeout per method call, specify a CancellationToken as a method argument and pass in the CancellationToken to timeout at any desired timing. Note that this cancellation does not propagate to the client; the client always receives default(CancellationToken).

interface IMyHubReceiver
{
    Task<string> DoLongWorkAsync(CancellationToken timeoutCancellationToken = default);
}
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await Client.DoLongWorkAsync(cts.Token);
Console.WriteLine(result);

Limitations