如何在请求启动的同一线程上调用代码
How to invoke code on same thread the request started
我有一个 POST 控制器方法在很多服务中使用异步等待,在控制器级别我需要发送一些 New Relic 参数。
当参数从非请求启动的线程发送时,New Relic 会给出警告日志。
NewRelic WARN: Agent API Error: An error occurred invoking API method
"AddCustomParameter" - "System.InvalidOperationException: The API
method called is only valid from within a transaction. This error can
occur if you call the API method from a thread other than the one the
transaction started on. at
NewRelic.Agent.Core.Api.AsyncAgentApi.GetCurrentTransactionBuilder()
at NewRelic.Agent.Core.Api.AsyncAgentApi.AddCustomParameter(String
key, String value)"
如何在我的控制器方法中调用将参数值发送到 New Relic 的代码?
例如,控制器中的以下代码。
var threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Before async method : {ThreadIdMessage(threadid)}");
var reportObject = await ReportService.GetReportAsync(requestModel).ConfigureAwait(true);
if (reportObject.PolicyModels != null)
{
threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Before sending New Relic values: {ThreadIdMessage(threadid)}");
AddPoliciesCountInNewRelic(reportObject.PolicyModels.Count);
AddTotalTransactionsCountInNewRelic(
reportObject.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"After sending New Relic values: {ThreadIdMessage(threadid)}");
}
将打印
DEBUG - Before async method : Current Thread Id: 5
DEBUG - Before sending New Relic values: Current Thread Id: 9
NewRelic.AddCustomParameter(CVPoliciesCount,2)
NewRelic.AddCustomParameter(CVTotalTransactionsCount,8)
DEBUG - After sending New Relic values: Current Thread Id: 9
根据 New Relic 警告日志,我应该在线程 ID 5 中调用 AddCustomParameter
方法。
AddPoliciesCountInNewRelic
和 AddTotalTransactionsCountInNewRelic
调用 ApiControllerBase.AddNewRelicParameter(string, string)
基础 class 保护方法。
private void AddPoliciesCountInNewRelic(int policiesCount)
{
AddNewRelicParameter("CVPoliciesCount", policiesCount.ToString());
}
private void AddTotalTransactionsCountInNewRelic(int transactionsCount)
{
AddNewRelicParameter("CVTotalTransactionsCount", transactionsCount.ToString());
}
protected void AddNewRelicParameter(string key, string value)
{
if (!string.IsNullOrWhiteSpace(key) &&
!string.IsNullOrWhiteSpace(value))
{
try
{
NewRelic.Api.Agent.NewRelic.AddCustomParameter(key, value);
}
catch (Exception ex)
{
Log.Error($"ERROR! : New Relic Parameter Exception {ex}");
}
}
}
如果您必须返回同一个线程,那么您真的无法退出该线程——无法保证您的线程会返回给您。 Async ... await 将正确恢复您的操作请求的 context,但不一定是同一线程。
换句话说,要么不要进行 async...await 调用并保留线程,要么在控制器方法中启动异步代码但阻塞并等待它完成(从而破坏目的async..await 代码,因为在等待 IO 完成时您不会为另一个操作方法生成线程)
我能够通过使用 continuation 来解决这个问题,但是就像@evk 在@Tim 回答中评论的那样,New Relic 应该考虑解决这个问题,这样我们就不必解决适当的代码来满足这样的要求。
var context = TaskScheduler.FromCurrentSynchronizationContext();
var threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Entry ThreadID: {threadid}");
var getReportTask = ReportService.GetReportAsync(requestModel);
getReportTask.ContinueWith(antecedent =>
{
var continuationThreadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"continuationThreadid: {continuationThreadid}");
var result = antecedent.Result;
if (result.PolicyModels != null)
{
AddPoliciesCountInNewRelic(result.PolicyModels.Count);
AddTotalTransactionsCountInNewRelic(
result.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
}
}, context);
var reportObject = await getReportTask.ConfigureAwait(false);
它将按预期打印相同的线程 ID。
DEBUG - Entry ThreadID: 5
DEBUG - continuationThreadid: 5
我有一个 POST 控制器方法在很多服务中使用异步等待,在控制器级别我需要发送一些 New Relic 参数。 当参数从非请求启动的线程发送时,New Relic 会给出警告日志。
NewRelic WARN: Agent API Error: An error occurred invoking API method "AddCustomParameter" - "System.InvalidOperationException: The API method called is only valid from within a transaction. This error can occur if you call the API method from a thread other than the one the transaction started on. at NewRelic.Agent.Core.Api.AsyncAgentApi.GetCurrentTransactionBuilder()
at NewRelic.Agent.Core.Api.AsyncAgentApi.AddCustomParameter(String key, String value)"
如何在我的控制器方法中调用将参数值发送到 New Relic 的代码?
例如,控制器中的以下代码。
var threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Before async method : {ThreadIdMessage(threadid)}");
var reportObject = await ReportService.GetReportAsync(requestModel).ConfigureAwait(true);
if (reportObject.PolicyModels != null)
{
threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Before sending New Relic values: {ThreadIdMessage(threadid)}");
AddPoliciesCountInNewRelic(reportObject.PolicyModels.Count);
AddTotalTransactionsCountInNewRelic(
reportObject.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"After sending New Relic values: {ThreadIdMessage(threadid)}");
}
将打印
DEBUG - Before async method : Current Thread Id: 5
DEBUG - Before sending New Relic values: Current Thread Id: 9
NewRelic.AddCustomParameter(CVPoliciesCount,2)
NewRelic.AddCustomParameter(CVTotalTransactionsCount,8)
DEBUG - After sending New Relic values: Current Thread Id: 9
根据 New Relic 警告日志,我应该在线程 ID 5 中调用 AddCustomParameter
方法。
AddPoliciesCountInNewRelic
和 AddTotalTransactionsCountInNewRelic
调用 ApiControllerBase.AddNewRelicParameter(string, string)
基础 class 保护方法。
private void AddPoliciesCountInNewRelic(int policiesCount)
{
AddNewRelicParameter("CVPoliciesCount", policiesCount.ToString());
}
private void AddTotalTransactionsCountInNewRelic(int transactionsCount)
{
AddNewRelicParameter("CVTotalTransactionsCount", transactionsCount.ToString());
}
protected void AddNewRelicParameter(string key, string value)
{
if (!string.IsNullOrWhiteSpace(key) &&
!string.IsNullOrWhiteSpace(value))
{
try
{
NewRelic.Api.Agent.NewRelic.AddCustomParameter(key, value);
}
catch (Exception ex)
{
Log.Error($"ERROR! : New Relic Parameter Exception {ex}");
}
}
}
如果您必须返回同一个线程,那么您真的无法退出该线程——无法保证您的线程会返回给您。 Async ... await 将正确恢复您的操作请求的 context,但不一定是同一线程。
换句话说,要么不要进行 async...await 调用并保留线程,要么在控制器方法中启动异步代码但阻塞并等待它完成(从而破坏目的async..await 代码,因为在等待 IO 完成时您不会为另一个操作方法生成线程)
我能够通过使用 continuation 来解决这个问题,但是就像@evk 在@Tim 回答中评论的那样,New Relic 应该考虑解决这个问题,这样我们就不必解决适当的代码来满足这样的要求。
var context = TaskScheduler.FromCurrentSynchronizationContext();
var threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Entry ThreadID: {threadid}");
var getReportTask = ReportService.GetReportAsync(requestModel);
getReportTask.ContinueWith(antecedent =>
{
var continuationThreadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"continuationThreadid: {continuationThreadid}");
var result = antecedent.Result;
if (result.PolicyModels != null)
{
AddPoliciesCountInNewRelic(result.PolicyModels.Count);
AddTotalTransactionsCountInNewRelic(
result.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
}
}, context);
var reportObject = await getReportTask.ConfigureAwait(false);
它将按预期打印相同的线程 ID。
DEBUG - Entry ThreadID: 5
DEBUG - continuationThreadid: 5