从 TaskCompletionSource 获取方法的 return 值
Get return value of method from TaskCompletionSource
我有一个生成 PDF 文件的方法 RenderReport
(byte[]
)。这有时会无限期挂起。成功时应该不会超过 15 秒即可完成。因此,我使用 TaskCompletionSource
来限制执行时间并在超过超时时抛出 TimeoutException
。
但是,我无法确定的是:如何将RenderReport
返回的byte[]
文件提供给以下代码中的SetResult
? longRunningTask.Wait
returns 一个布尔值而不是文件那么你从哪里得到文件?
我不想使用 longRunningTask.Result
,因为 introduce deadlock issues。这是我的代码:
public async Task RunLongRunningTaskAsync()
{
Task<byte[]> longRunningTask = Task.Run(() => RenderReport());
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
Task toBeAwaited = tcs.Task;
new Thread(() => ThreadBody(longRunningTask, tcs, 15)).Start();
await toBeAwaited;
}
private void ThreadBody(Task<byte[]> longRunningTask, TaskCompletionSource<byte[]> tcs, int seconds)
{
bool completed = longRunningTask.Wait(TimeSpan.FromSeconds(seconds));
if (completed)
// The result is a placeholder. How do you get the return value of the RenderReport()?
tcs.SetResult(new byte[100]);
else
tcs.SetException(new TimeoutException("error!"));
}
private byte[] RenderReport()
{
using (var report = new Microsoft.Reporting.WinForms.LocalReport())
{
// Other logic here...
var file = report.Render("PDF", null, out _, out _, out _, out _, out var warnings);
if (warnings.Any())
{
// Log warnings...
}
return file; // How do I get this file?
}
}
对我有用的是使用 ContinueWith
:
这显示第一个 运行 在指定的 2 秒内成功检索,然后是第二个 运行 超时。
所以这只是一种方法,但这有什么用吗?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task_completion
{
class Program
{
static void Main(string[] args)
{
runAsync();
Console.ReadKey();
}
static async void runAsync()
{
// Normal
await longRunningByteGetter(1000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t)=>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
// TimeOut
await longRunningByteGetter(3000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t) =>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
}
async static Task<Byte[]> longRunningByteGetter(int delay, CancellationToken token)
{
await Task.Delay(delay, token); // This is a mock of your file retrieval
return new byte[100];
}
}
}
如果您同步等待 Task
完成,您只会冒死锁的风险。
如果您知道 longRunningTask
已经完成,那么访问 longRunningTask.Result
是绝对安全的。所以就这样做:
if (completed)
tcs.SetResult(longRunningTask.Result);
else
tcs.SetException(new TimeoutException("error!"));
甚至比这更复杂:即使您同步等待 longRunningTask
完成,您的案例也不会死锁,因为 longRunningTask
不依赖于任何等待。很值得理解为什么会出现这种死锁情况。
也就是说,放弃一个卡在某个地方的线程听起来是个糟糕的主意:该线程将永远存在泄漏资源,随着更多报告挂起,您将积累它们,直到您 运行内存不足。
如果您真的搞不清楚发生了什么,我建议围绕报告生成编写一个小型包装应用程序,然后 运行 将其作为一个单独的过程。终止进程是 well-supported,让您确保没有任何泄漏。您可以 return 通过标准输出返回报告的字节数。
我有一个生成 PDF 文件的方法 RenderReport
(byte[]
)。这有时会无限期挂起。成功时应该不会超过 15 秒即可完成。因此,我使用 TaskCompletionSource
来限制执行时间并在超过超时时抛出 TimeoutException
。
但是,我无法确定的是:如何将RenderReport
返回的byte[]
文件提供给以下代码中的SetResult
? longRunningTask.Wait
returns 一个布尔值而不是文件那么你从哪里得到文件?
我不想使用 longRunningTask.Result
,因为 introduce deadlock issues。这是我的代码:
public async Task RunLongRunningTaskAsync()
{
Task<byte[]> longRunningTask = Task.Run(() => RenderReport());
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
Task toBeAwaited = tcs.Task;
new Thread(() => ThreadBody(longRunningTask, tcs, 15)).Start();
await toBeAwaited;
}
private void ThreadBody(Task<byte[]> longRunningTask, TaskCompletionSource<byte[]> tcs, int seconds)
{
bool completed = longRunningTask.Wait(TimeSpan.FromSeconds(seconds));
if (completed)
// The result is a placeholder. How do you get the return value of the RenderReport()?
tcs.SetResult(new byte[100]);
else
tcs.SetException(new TimeoutException("error!"));
}
private byte[] RenderReport()
{
using (var report = new Microsoft.Reporting.WinForms.LocalReport())
{
// Other logic here...
var file = report.Render("PDF", null, out _, out _, out _, out _, out var warnings);
if (warnings.Any())
{
// Log warnings...
}
return file; // How do I get this file?
}
}
对我有用的是使用 ContinueWith
:
这显示第一个 运行 在指定的 2 秒内成功检索,然后是第二个 运行 超时。
所以这只是一种方法,但这有什么用吗?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task_completion
{
class Program
{
static void Main(string[] args)
{
runAsync();
Console.ReadKey();
}
static async void runAsync()
{
// Normal
await longRunningByteGetter(1000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t)=>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
// TimeOut
await longRunningByteGetter(3000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t) =>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
}
async static Task<Byte[]> longRunningByteGetter(int delay, CancellationToken token)
{
await Task.Delay(delay, token); // This is a mock of your file retrieval
return new byte[100];
}
}
}
如果您同步等待 Task
完成,您只会冒死锁的风险。
如果您知道 longRunningTask
已经完成,那么访问 longRunningTask.Result
是绝对安全的。所以就这样做:
if (completed)
tcs.SetResult(longRunningTask.Result);
else
tcs.SetException(new TimeoutException("error!"));
甚至比这更复杂:即使您同步等待 longRunningTask
完成,您的案例也不会死锁,因为 longRunningTask
不依赖于任何等待。很值得理解为什么会出现这种死锁情况。
也就是说,放弃一个卡在某个地方的线程听起来是个糟糕的主意:该线程将永远存在泄漏资源,随着更多报告挂起,您将积累它们,直到您 运行内存不足。
如果您真的搞不清楚发生了什么,我建议围绕报告生成编写一个小型包装应用程序,然后 运行 将其作为一个单独的过程。终止进程是 well-supported,让您确保没有任何泄漏。您可以 return 通过标准输出返回报告的字节数。