diff --git a/src/csharp/Grpc.Core.Api/ChannelBase.cs b/src/csharp/Grpc.Core.Api/ChannelBase.cs index 54546300e81..d6a5481d162 100644 --- a/src/csharp/Grpc.Core.Api/ChannelBase.cs +++ b/src/csharp/Grpc.Core.Api/ChannelBase.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.Threading.Tasks; using Grpc.Core.Utils; namespace Grpc.Core @@ -48,5 +49,34 @@ namespace Grpc.Core /// /// A new . public abstract CallInvoker CreateCallInvoker(); + + /// + /// Shuts down the channel cleanly. It is strongly recommended to shutdown + /// the channel once you stopped using it. + /// + /// + /// Guidance for implementors: + /// This method doesn't wait for all calls on this channel to finish (nor does + /// it have to explicitly cancel all outstanding calls). It is user's responsibility to make sure + /// all the calls on this channel have finished (successfully or with an error) + /// before shutting down the channel to ensure channel shutdown won't impact + /// the outcome of those remote calls. + /// + public Task ShutdownAsync() + { + return ShutdownAsyncCore(); + } + + /// Provides implementation of a non-virtual public member. + #pragma warning disable 1998 + protected virtual async Task ShutdownAsyncCore() + { + // default implementation is no-op for backwards compatibility, but all implementations + // are expected to override this method. + + // warning 1998 is disabled to avoid needing TaskUtils.CompletedTask, which is + // only available in Grpc.Core + } + #pragma warning restore 1998 } } diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs index 50ef10e7aec..9f9dedaa5ae 100644 --- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs +++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs @@ -113,5 +113,15 @@ namespace Grpc.Core.Tests Assert.Throws(typeof(ObjectDisposedException), () => { var x = channel.ResolvedTarget; }); Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await channel.ConnectAsync()); } + + [Test] + public async Task ChannelBaseShutdownAsyncInvokesShutdownAsync() + { + var channel = new Channel("localhost", ChannelCredentials.Insecure); + ChannelBase channelBase = channel; + await channelBase.ShutdownAsync(); + // check that Channel.ShutdownAsync has run + Assert.AreEqual(ChannelState.Shutdown, channel.State); + } } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index 8d1fb921ff4..81b7f1f1037 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -210,18 +210,8 @@ namespace Grpc.Core } } - /// - /// Shuts down the channel cleanly. It is strongly recommended to shutdown - /// all previously created channels before exiting from the process. - /// - /// - /// This method doesn't wait for all calls on this channel to finish (nor does - /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure - /// all the calls on this channel have finished (successfully or with an error) - /// before shutting down the channel to ensure channel shutdown won't impact - /// the outcome of those remote calls. - /// - public async Task ShutdownAsync() + /// Provides implementation of a non-virtual public member. + protected override async Task ShutdownAsyncCore() { lock (myLock) {