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)
{