Bagikan melalui


Membuat layanan dan metode gRPC

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh James Newton-King

Dokumen ini menjelaskan cara membuat layanan dan metode gRPC di C#. Topik meliputi:

  • Cara menentukan layanan dan metode dalam .proto file.
  • Kode yang dihasilkan menggunakan alat gRPC C#.
  • Menerapkan layanan dan metode gRPC.

Membuat layanan gRPC baru

Layanan gRPC dengan C# memperkenalkan pendekatan pertama kontrak gRPC untuk pengembangan API. Layanan dan pesan ditentukan dalam .proto file. Alat C# kemudian menghasilkan kode dari .proto file. Untuk aset sisi server, jenis dasar abstrak dihasilkan untuk setiap layanan, bersama dengan kelas untuk pesan apa pun.

File berikut .proto :

  • Greeter Menentukan layanan.
  • Layanan Greeter mendefinisikan SayHello panggilan.
  • SayHelloHelloRequest mengirim pesan dan menerima HelloReply pesan
syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Alat C# menghasilkan jenis dasar C# GreeterBase :

public abstract partial class GreeterBase
{
    public virtual Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
    }
}

public class HelloRequest
{
    public string Name { get; set; }
}

public class HelloReply
{
    public string Message { get; set; }
}

Secara default yang dihasilkan GreeterBase tidak melakukan apa pun. Metode virtualnya SayHello akan mengembalikan kesalahan UNIMPLEMENTED ke klien mana pun yang menyebutnya. Agar layanan dapat berguna, aplikasi harus membuat implementasi konkret dari GreeterBase:

public class GreeterService : GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
    }
}

ServerCallContext memberikan konteks untuk panggilan sisi server.

Implementasi layanan terdaftar di aplikasi. Jika layanan dihosting oleh ASP.NET Core gRPC, layanan harus ditambahkan ke alur perutean dengan MapGrpcService metode .

app.MapGrpcService<GreeterService>();

Lihat layanan gRPC dengan ASP.NET Core untuk informasi selengkapnya.

Menerapkan metode gRPC

Layanan gRPC dapat memiliki berbagai jenis metode. Bagaimana pesan dikirim dan diterima oleh layanan tergantung pada jenis metode yang ditentukan. Jenis metode gRPC adalah:

  • Unary
  • Streaming server
  • Streaming klien
  • Streaming dua arah

Panggilan streaming ditentukan dengan stream kata kunci dalam .proto file. stream dapat ditempatkan pada pesan permintaan panggilan, pesan respons, atau keduanya.

syntax = "proto3";

service ExampleService {
  // Unary
  rpc UnaryCall (ExampleRequest) returns (ExampleResponse);

  // Server streaming
  rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);

  // Client streaming
  rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);

  // Bi-directional streaming
  rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse);
}

Setiap jenis panggilan memiliki tanda tangan metode yang berbeda. Mengambil alih metode yang dihasilkan dari jenis layanan dasar abstrak dalam implementasi konkret memastikan argumen yang benar dan jenis pengembalian digunakan.

Metode unary

Metode unary memiliki pesan permintaan sebagai parameter, dan mengembalikan respons. Panggilan tidak sah selesai saat respons dikembalikan.

public override Task<ExampleResponse> UnaryCall(ExampleRequest request,
    ServerCallContext context)
{
    var response = new ExampleResponse();
    return Task.FromResult(response);
}

Panggilan unary adalah yang paling mirip dengan tindakan pada pengontrol API web. Salah satu perbedaan penting yang dimiliki metode gRPC dari tindakan adalah metode gRPC tidak dapat mengikat bagian permintaan ke argumen metode yang berbeda. Metode gRPC selalu memiliki satu argumen pesan untuk data permintaan masuk. Beberapa nilai masih dapat dikirim ke layanan gRPC dengan menambahkan bidang ke pesan permintaan:

message ExampleRequest {
    int32 pageIndex = 1;
    int32 pageSize = 2;
    bool isDescending = 3;
}

Metode streaming server

Metode streaming server memiliki pesan permintaan sebagai parameter. Karena beberapa pesan dapat dialirkan kembali ke pemanggil, responseStream.WriteAsync digunakan untuk mengirim pesan respons. Panggilan streaming server selesai ketika metode kembali.

public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    for (var i = 0; i < 5; i++)
    {
        await responseStream.WriteAsync(new ExampleResponse());
        await Task.Delay(TimeSpan.FromSeconds(1));
    }
}

Klien tidak memiliki cara untuk mengirim pesan atau data tambahan setelah metode streaming server dimulai. Beberapa metode streaming dirancang untuk berjalan selamanya. Untuk metode streaming berkelanjutan, klien dapat membatalkan panggilan saat tidak lagi diperlukan. Ketika pembatalan terjadi, klien mengirim sinyal ke server dan ServerCallContext.CancellationToken dinaikkan. Token CancellationToken harus digunakan pada server dengan metode asinkron sehingga:

  • Setiap pekerjaan asinkron dibatalkan bersama dengan panggilan streaming.
  • Metode keluar dengan cepat.
public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    while (!context.CancellationToken.IsCancellationRequested)
    {
        await responseStream.WriteAsync(new ExampleResponse());
        await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
    }
}

Metode streaming klien

Metode streaming klien dimulai tanpa metode menerima pesan. Parameter requestStream digunakan untuk membaca pesan dari klien. Panggilan streaming klien selesai saat pesan respons dikembalikan:

public override async Task<ExampleResponse> StreamingFromClient(
    IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)
{
    await foreach (var message in requestStream.ReadAllAsync())
    {
        // ...
    }
    return new ExampleResponse();
}

Metode streaming dua arah

Metode streaming dua arah dimulai tanpa metode menerima pesan. Parameter requestStream digunakan untuk membaca pesan dari klien. Metode ini dapat memilih untuk mengirim pesan dengan responseStream.WriteAsync. Panggilan streaming dua arah selesai saat metode mengembalikan:

public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    await foreach (var message in requestStream.ReadAllAsync())
    {
        await responseStream.WriteAsync(new ExampleResponse());
    }
}

Kode sebelumnya:

  • Mengirim respons untuk setiap permintaan.
  • Adalah penggunaan dasar streaming dua arah.

Dimungkinkan untuk mendukung skenario yang lebih kompleks, seperti membaca permintaan dan mengirim respons secara bersamaan:

public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    // Read requests in a background task.
    var readTask = Task.Run(async () =>
    {
        await foreach (var message in requestStream.ReadAllAsync())
        {
            // Process request.
        }
    });

    // Send responses until the client signals that it is complete.
    while (!readTask.IsCompleted)
    {
        await responseStream.WriteAsync(new ExampleResponse());
        await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
    }
}

Dalam metode streaming dua arah, klien dan layanan dapat mengirim pesan satu sama lain kapan saja. Implementasi terbaik dari metode dua arah bervariasi tergantung pada persyaratan.

Mengakses header permintaan gRPC

Pesan permintaan bukan satu-satunya cara bagi klien untuk mengirim data ke layanan gRPC. Nilai header tersedia dalam layanan menggunakan ServerCallContext.RequestHeaders.

public override Task<ExampleResponse> UnaryCall(ExampleRequest request,
    ServerCallContext context)
{
    var userAgent = context.RequestHeaders.GetValue("user-agent");
    // ...

    return Task.FromResult(new ExampleResponse());
}

Multi-utas dengan metode streaming gRPC

Ada pertimbangan penting untuk menerapkan metode streaming gRPC yang menggunakan beberapa utas.

Keamanan utas pembaca dan penulis

IAsyncStreamReader<TMessage> dan IServerStreamWriter<TMessage> masing-masing hanya dapat digunakan oleh satu utas pada satu waktu. Untuk metode streaming gRPC, beberapa utas tidak dapat membaca pesan baru secara requestStream.MoveNext() bersamaan. Dan beberapa utas tidak dapat menulis pesan baru secara responseStream.WriteAsync(message) bersamaan.

Cara aman untuk mengaktifkan beberapa utas untuk berinteraksi dengan metode gRPC adalah dengan menggunakan pola produsen-konsumen dengan System.Threading.Channels.

public override async Task DownloadResults(DataRequest request,
        IServerStreamWriter<DataResult> responseStream, ServerCallContext context)
{
    var channel = Channel.CreateBounded<DataResult>(new BoundedChannelOptions(capacity: 5));

    var consumerTask = Task.Run(async () =>
    {
        // Consume messages from channel and write to response stream.
        await foreach (var message in channel.Reader.ReadAllAsync())
        {
            await responseStream.WriteAsync(message);
        }
    });

    var dataChunks = request.Value.Chunk(size: 10);

    // Write messages to channel from multiple threads.
    await Task.WhenAll(dataChunks.Select(
        async c =>
        {
            var message = new DataResult { BytesProcessed = c.Length };
            await channel.Writer.WriteAsync(message);
        }));

    // Complete writing and wait for consumer to complete.
    channel.Writer.Complete();
    await consumerTask;
}

Metode streaming server gRPC sebelumnya:

  • Membuat saluran terikat untuk memproduksi dan mengonsumsi DataResult pesan.
  • Memulai tugas untuk membaca pesan dari saluran dan menulisnya ke aliran respons.
  • Menulis pesan ke saluran dari beberapa utas.

Catatan

Metode streaming dua arah mengambil IAsyncStreamReader<TMessage> dan IServerStreamWriter<TMessage> sebagai argumen. Aman untuk menggunakan jenis ini pada utas terpisah satu sama lain.

Berinteraksi dengan metode gRPC setelah panggilan berakhir

Panggilan gRPC berakhir di server setelah metode gRPC keluar. Argumen berikut yang diteruskan ke metode gRPC tidak aman digunakan setelah panggilan berakhir:

  • ServerCallContext
  • IAsyncStreamReader<TMessage>
  • IServerStreamWriter<TMessage>

Jika metode gRPC memulai tugas latar belakang yang menggunakan jenis ini, metode tersebut harus menyelesaikan tugas sebelum metode gRPC keluar. Terus menggunakan konteks, pembaca streaming, atau penulis streaming setelah metode gRPC ada menyebabkan kesalahan dan perilaku yang tidak dapat diprediksi.

Dalam contoh berikut, metode streaming server dapat menulis ke aliran respons setelah panggilan selesai:

public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    _ = Task.Run(async () =>
    {
        for (var i = 0; i < 5; i++)
        {
            await responseStream.WriteAsync(new ExampleResponse());
            await Task.Delay(TimeSpan.FromSeconds(1));
        }
    });

    await PerformLongRunningWorkAsync();
}

Untuk contoh sebelumnya, solusinya adalah menunggu tugas tulis sebelum keluar dari metode:

public override async Task StreamingFromServer(ExampleRequest request,
    IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
    var writeTask = Task.Run(async () =>
    {
        for (var i = 0; i < 5; i++)
        {
            await responseStream.WriteAsync(new ExampleResponse());
            await Task.Delay(TimeSpan.FromSeconds(1));
        }
    });

    await PerformLongRunningWorkAsync();

    await writeTask;
}

Sumber Daya Tambahan: