使用 TaskCompletionSource 的异步方法中的死锁
Deadlocks in async method using TaskCompletionSource
我正在尝试公开一个 ASP.NET Web API,它必须捕获 return 一张照片作为附件。
我正在使用 CameraControl.Devices 2.1.0-beta 来控制我的 Nikon DSLR 和 .NET 4.6。
但是我的行为非常不一致。有时,第一个 HTTP 请求设法 return 一张照片,但从那里开始,它在编写最终的 HttpResponseMessage 之前就停止了,然后 HTTP 请求就挂起并超时。我相信这是由异步操作引起的死锁。它始终执行 DeviceManager_PhotoCaptured 中的逻辑,将照片从 DSLR 传输到文件夹。有时它甚至会从一个请求中拍摄两张照片。
我做错了什么?你知道更好的方法吗?
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using CameraControl.Devices;
using CameraControl.Devices.Classes;
public class PhotosController : ApiController
{
public string FolderForPhotos = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Test");
private TaskCompletionSource<string> TaskCompletionSource = null;
private CameraDeviceManager DeviceManager = null;
// GET api/photos
public async Task<HttpResponseMessage> Get()
{
TaskCompletionSource = new TaskCompletionSource<string>();
DeviceManager = new CameraDeviceManager();
DeviceManager.StartInNewThread = false;
DeviceManager.PhotoCaptured += DeviceManager_PhotoCaptured;
DeviceManager.ConnectToCamera();
DeviceManager.SelectedCameraDevice.CapturePhoto();
var fileName = await TaskCompletionSource.Task.ConfigureAwait(continueOnCapturedContext: false);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(fileName, FileMode.Open);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(fileName);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpg");
result.Content.Headers.ContentLength = stream.Length;
return result;
}
private void DeviceManager_PhotoCaptured(object sender, PhotoCapturedEventArgs eventArgs)
{
if (eventArgs == null)
{
TaskCompletionSource.TrySetException(new Exception("eventArgs is empty"));
return;
}
try
{
string fileName = Path.Combine(FolderForPhotos, Path.GetFileName(eventArgs.FileName));
// if file exist try to generate a new filename to prevent file lost.
// This useful when camera is set to record in ram the the all file names are same.
if (File.Exists(fileName))
fileName =
StaticHelper.GetUniqueFilename(
Path.GetDirectoryName(fileName) + "\" + Path.GetFileNameWithoutExtension(fileName) + "_", 0,
Path.GetExtension(fileName));
// check the folder of filename, if not found create it
if (!Directory.Exists(Path.GetDirectoryName(fileName)))
{
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
}
eventArgs.CameraDevice.TransferFile(eventArgs.Handle, fileName);
// the IsBusy may used internally, if file transfer is done should set to false
eventArgs.CameraDevice.IsBusy = false;
TaskCompletionSource.TrySetResult(fileName);
}
catch (Exception exception)
{
TaskCompletionSource.TrySetException(exception);
eventArgs.CameraDevice.IsBusy = false;
}
finally
{
DeviceManager.CloseAll();
}
}
}
我想通了:
我必须将 TaskCompletionSource
设置为静态字段。我想,出于某种原因,当它是一个实例字段时,它最终被释放或分离。
private static TaskCompletionSource<string> TaskCompletionSource = null;
我正在尝试公开一个 ASP.NET Web API,它必须捕获 return 一张照片作为附件。 我正在使用 CameraControl.Devices 2.1.0-beta 来控制我的 Nikon DSLR 和 .NET 4.6。
但是我的行为非常不一致。有时,第一个 HTTP 请求设法 return 一张照片,但从那里开始,它在编写最终的 HttpResponseMessage 之前就停止了,然后 HTTP 请求就挂起并超时。我相信这是由异步操作引起的死锁。它始终执行 DeviceManager_PhotoCaptured 中的逻辑,将照片从 DSLR 传输到文件夹。有时它甚至会从一个请求中拍摄两张照片。
我做错了什么?你知道更好的方法吗?
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using CameraControl.Devices;
using CameraControl.Devices.Classes;
public class PhotosController : ApiController
{
public string FolderForPhotos = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Test");
private TaskCompletionSource<string> TaskCompletionSource = null;
private CameraDeviceManager DeviceManager = null;
// GET api/photos
public async Task<HttpResponseMessage> Get()
{
TaskCompletionSource = new TaskCompletionSource<string>();
DeviceManager = new CameraDeviceManager();
DeviceManager.StartInNewThread = false;
DeviceManager.PhotoCaptured += DeviceManager_PhotoCaptured;
DeviceManager.ConnectToCamera();
DeviceManager.SelectedCameraDevice.CapturePhoto();
var fileName = await TaskCompletionSource.Task.ConfigureAwait(continueOnCapturedContext: false);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(fileName, FileMode.Open);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(fileName);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpg");
result.Content.Headers.ContentLength = stream.Length;
return result;
}
private void DeviceManager_PhotoCaptured(object sender, PhotoCapturedEventArgs eventArgs)
{
if (eventArgs == null)
{
TaskCompletionSource.TrySetException(new Exception("eventArgs is empty"));
return;
}
try
{
string fileName = Path.Combine(FolderForPhotos, Path.GetFileName(eventArgs.FileName));
// if file exist try to generate a new filename to prevent file lost.
// This useful when camera is set to record in ram the the all file names are same.
if (File.Exists(fileName))
fileName =
StaticHelper.GetUniqueFilename(
Path.GetDirectoryName(fileName) + "\" + Path.GetFileNameWithoutExtension(fileName) + "_", 0,
Path.GetExtension(fileName));
// check the folder of filename, if not found create it
if (!Directory.Exists(Path.GetDirectoryName(fileName)))
{
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
}
eventArgs.CameraDevice.TransferFile(eventArgs.Handle, fileName);
// the IsBusy may used internally, if file transfer is done should set to false
eventArgs.CameraDevice.IsBusy = false;
TaskCompletionSource.TrySetResult(fileName);
}
catch (Exception exception)
{
TaskCompletionSource.TrySetException(exception);
eventArgs.CameraDevice.IsBusy = false;
}
finally
{
DeviceManager.CloseAll();
}
}
}
我想通了:
我必须将 TaskCompletionSource
设置为静态字段。我想,出于某种原因,当它是一个实例字段时,它最终被释放或分离。
private static TaskCompletionSource<string> TaskCompletionSource = null;