Google api Google DriveService C#泄漏HttpConnection对象

Google api Google DriveService C#泄漏HttpConnection对象,google-api,google-drive-api,google-api-dotnet-client,Google Api,Google Drive Api,Google Api Dotnet Client,我有一个使用C#SDK的应用程序,它利用谷歌的硬盘服务。我正在使用服务帐户凭据,并通过模拟指定我代表的用户 我遇到的问题是,我需要为每个模拟用户创建一个新的DriveService,这些DriveService isntances中的每一个都会创建两个HttpConnection对象,在我处置DriveService()时,这些对象不会被清理。我在单元测试中重新编写了这个。我使用以下方法创建DriveService: public DriveService GetDriveService(str

我有一个使用C#SDK的应用程序,它利用谷歌的硬盘服务。我正在使用服务帐户凭据,并通过模拟指定我代表的用户

我遇到的问题是,我需要为每个模拟用户创建一个新的DriveService,这些DriveService isntances中的每一个都会创建两个HttpConnection对象,在我处置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
                        }
                    }
                }
            }
        }
public异步任务DoTheGDriveMemoryTest()
{
var userEmails=新列表
{
"email1@example.com", "email2@example.com"
};
var i=1;
while(true)
{
foreach(用户电子邮件中的var电子邮件)
{
//建议在这里使用Fiddler自动应答器
使用(var服务=GetDriveService(电子邮件))
{
var请求=service.Changes.List(“增量id”);
尝试
{
var result=wait request.ExecuteAsync();
}
捕获(例外e)
{
//无所事事
}
}
}
}
}

这导致存储在内存中的HttpConnection对象数量不断增加,而这些对象不会通过垃圾收集进行清理。

如果DriveService在幕后使用HttpClient,则可能会遇到dispose无法处理共享对象,或者连接会一直保持到超时

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


参见

正如Mike M所述,这是HttpClient的一个已知问题,因为它的
Dispose()
方法实际上没有处理实例

不幸的是,由于
Google.api.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;
    }
}
然后使用此工厂创建每个
DriveService

var factory = new SharedHttpClientFactory();
var client = new StorageService(new Google.Apis.Services.BaseClientService.Initializer
{
    HttpClientFactory = factory,
    HttpClientInitializer = serviceAcctCreds
});
警告比比皆是:

  • 不要处置
    DriveService
    ,因为这将处置底层共享
    HttpClient
  • 所有
    DriveService
    实例共享相同的凭据;每次实例化新的
    DriveService
    时,都会更改这些凭据。这意味着所有以前的实例的凭据都已更新
  • 传递到初始值设定项
    HttpClientInitializer
    属性的凭据必须是
    ServiceCredential
    实例(与您的代码相同)。如果它是一个
    GoogleCredential
    实例,这将不起作用
  • 这整件事并不是真的建议,绝对是一个令人讨厌的黑客行为,但我看不到更好/更简单的解决方法

相关文章:我们正在应用程序的其他领域重新使用HttpClient,但这一点是我们无法控制的。当模拟用户时,我们被迫为每个用户创建一个新的HttpClientInitializer,这意味着如果我们有10k个用户,我们就有10k HttpClientInitializer和10k DriveService实例,它们都有自己的HttpClient。我得到了一些与HttpClient的基本用法相关的评论,这些评论是有效的。这里的问题是,当使用服务帐户进行身份验证时,我们必须提供ServiceAccountCredentials的“User”属性,以指定我们在特定调用中模拟的用户。这使我们无法重新使用DriveService,而DriveService正是包含HttpClient的地方。所以,也许一个更合适的问题是“如何为模拟用户重新使用DriveService”。谢谢您的输入。第二个警告可能是这不起作用的主要原因:我正在为不同的用户在不同的线程上创建DriveService实例,因此需要能够更改凭据。看来我只需要编写自己的代码就可以实现RESTAPI。