Google DriveService C#泄漏HttpConnection对象

时间:2018-11-01 15:34:09

标签: google-api google-drive-api google-api-dotnet-client

我有一个使用C#SDK利用Google云端硬盘服务的应用程序。我正在使用服务帐户凭据(具有模拟功能)来指定我代表的用户。

我遇到的问题是,我需要为每个模拟用户创建一个新的DriveService,并且每个DriveService实例都会创建2个HttpConnection对象,当我Dispose()DriveService时,这些对象不会被清除。我已经在单元测试中对此进行了复制。我通过以下方法创建DriveService:

public DriveService GetDriveService(string email)
    {
        var serviceAccountEmail = "service account email";
        var privateKey = "private key";

        var serviceAcctCreds =  new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
        {
            User = email,
            Scopes = "scopes" }), 
        }.FromPrivateKey(privateKey));

        var driveService = new DriveService(new BaseClientService.Initializer
        { 
            HttpClientInitializer = serviceAcctCreds,
            ApplicationName = "app name",
        });

        return driveService;
    }

然后按如下方式使用它:

public async Task DoTheGDriveMemoryTest()
        {
            var userEmails = new List<string>
            {
                 "email1@example.com", "email2@example.com"
            };
            var i = 1;
            while (true)
            {
                foreach (var email in userEmails)
                {
                    //recommend using Fiddler Autoresponders here
                    using (var service = GetDriveService(email))
                    {
                        var request = service.Changes.List("delta id");                        
                        try
                        {
                            var result = await request.ExecuteAsync();
                        }
                        catch (Exception e)
                        {
                            //do nothing
                        }
                    }
                }
            }
        }

这导致内存中保存的HttpConnection对象数量不断增加,而这些对象无法通过垃圾回收清除。

2 个答案:

答案 0 :(得分:0)

如果DriveService在后台使用HttpClient,则您可能会遇到无法在共享库上工作或在超时之前保持连接的情况。

  

但是HttpClient是不同的。尽管它实现了IDisposable接口,但实际上是一个共享对象。

请参见https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

答案 1 :(得分:0)

正如Mike M所言,HttpClient的一个已知问题在于它的Dispose()方法并未真正处理该实例。

不幸的是,由于Google.Apis.Auth库将HttpClient和凭据结合在一起的方式,我看不到解决此问题的好方法。

这是一个非常棘手的解决方法,它允许您在所有RPC之间共享一个HttpClient。 定义自定义IHttpClientFactory

class SharedHttpClientFactory : IHttpClientFactory
{
    public SharedHttpClientFactory()
    {
        _handler = new ConfigurableMessageHandler(new HttpClientHandler());
        _client = new ConfigurableHttpClient(_handler);
    }

    private readonly ConfigurableMessageHandler _handler;
    private readonly ConfigurableHttpClient _client;
    private Action _clearHandlers = null;

    public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args)
    {
        _clearHandlers?.Invoke();
        var executeInterceptors = args.Initializers.OfType<IHttpExecuteInterceptor>().ToList();
        var unsuccessfulResponseHandlers = args.Initializers.OfType<IHttpUnsuccessfulResponseHandler>().ToList();
        _clearHandlers = () =>
        {
            foreach (var executeInterceptor in executeInterceptors)
            {
                _handler.RemoveExecuteInterceptor(executeInterceptor);
            }
            foreach (var unsuccssfulResponseHandler in unsuccessfulResponseHandlers)
            {
                _handler.RemoveUnsuccessfulResponseHandler(unsuccssfulResponseHandler);
            }
        };
        foreach (var executeInterceptor in executeInterceptors)
        {
            _handler.AddExecuteInterceptor(executeInterceptor);
        }
        foreach (var unsuccssfulResponseHandler in unsuccessfulResponseHandlers)
        {
            _handler.AddUnsuccessfulResponseHandler(unsuccssfulResponseHandler);
        }
        _handler.ApplicationName = args.ApplicationName;
        return _client;
    }
}

创建该工厂的单个实例:

var factory = new SharedHttpClientFactory();

然后使用此工厂创建每个DriveService

var client = new StorageService(new Google.Apis.Services.BaseClientService.Initializer
{
    HttpClientFactory = factory,
    HttpClientInitializer = serviceAcctCreds
});

大量注意事项:

  • 请勿处置DriveService,因为这将处置基础共享HttpClient
  • 所有DriveService实例共享相同的凭据;每次实例化新的DriveService时,这些凭据都会更改。这意味着所有以前的实例也已更新其凭据。
  • 传递给初始化器HttpClientInitializer属性 的凭据必须是ServiceCredential实例(与您的代码一样)。如果它是GoogleCredential实例,则无法使用。
  • 这整个事情并没有真正的建议,绝对是一个令人讨厌的骇客,但是我看不到更好/更简单的解决方法。