我如何使用方法async Task 并返回字符串,然后如何将该方法的委托传递给构造函数并在以后使用它?
我有这个返回string
方法:
public string SendResponse(HttpListenerRequest request) { string result = ""; string key = request.QueryString.GetKey(0); if (key == "cmd") { if (request.QueryString[0] == "uploadstatus") { switch (Youtube_Uploader.uploadstatus) { case "uploading file": return "uploading " + Youtube_Uploader.fileuploadpercentages; case "status": return Youtube_Uploader.fileuploadpercentages.ToString(); case "file uploaded successfully": Youtube_Uploader.uploadstatus = ""; return "upload completed," + Youtube_Uploader.fileuploadpercentages + "," + Youtube_Uploader.time; default: return "upload unknown state"; } } if (request.QueryString[0] == "nothing") { return "Connection Success"; } if (request.QueryString[0] == "start") { StartRecrod(); result = "Recording started"; } if (request.QueryString[0] == "stop") { dirchanged = false; StartRecrod(); result = "Recording stopped and preparing the file to be shared on youtube"; string fileforupload = await WatchDirectory(); await WaitForUnlockedFile(fileforupload); using (StreamWriter w = new StreamWriter(userVideosDirectory + "\\UploadedVideoFiles.txt", true)) { w.WriteLine(fileforupload); } uploadedFilesList.Add(fileforupload); Youtube_Uploader youtubeupload = new Youtube_Uploader(uploadedFilesList[0]); } } else { result = "Nothing have been done"; } return result; }
问题是在这个方法中我用两行await
:
string fileforupload = await WatchDirectory(); await WaitForUnlockedFile(fileforupload);
并在这两行上得到错误。 两个错误都是一样的:
错误’await’运算符只能在异步方法中使用。 考虑使用’async’修饰符标记此方法并将其返回类型更改为
'Task'
。
问题是,是否有可能使SendResponse()
方法返回字符串,因为我需要它并使用await
?
这就是我需要在SendResponse()
方法中使用await
的两种方法:
private async Task WatchDirectory() { using (FileSystemWatcher watcher = new FileSystemWatcher()) { TaskCompletionSource tcs = new TaskCompletionSource(); watcher.Path = userVideosDirectory; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size; watcher.Filter = "*.mp4"; watcher.Changed += (sender, e) => tcs.SetResult(e.FullPath); watcher.EnableRaisingEvents = true; return await tcs.Task; } } // You can get rid of the OnChanged() method altogether private async Task WaitForUnlockedFile(string fileName) { while (true) { try { using (IDisposable stream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { /* on success, immediately dispose object */ } break; } catch (IOException) { // ignore exception // NOTE: for best results, consider checking the hresult value of // the exception, to ensure that you are only ignoring the access violation // exception you're expecting, rather than other exceptions, like // FileNotFoundException, etc. which could result in a hung process } // You might want to consider a longer delay...maybe on the order of // a second or two at least. await Task.Delay(100); } }
更新:
我将方法SendResponse()
更改为async Task
但是在WebServer
类构造函数中我有这个并且在此行上收到错误:
WebServer ws = new WebServer(SendResponseAsync, "http://+:8098/");
(SendResponseAsync是SendResponse更名)
错误是:
错误1’System.Threading.Tasks.Task Automatic_Record.Form1.SendResponseAync(System.Net.HttpListenerRequest)’具有错误的返回类型
而WebServer类是:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Threading; namespace Automatic_Record { class WebServer { private readonly HttpListener _listener = new HttpListener(); private readonly Func _responderMethod; public WebServer(string[] prefixes, Func method) { if (!HttpListener.IsSupported) throw new NotSupportedException( "Needs Windows XP SP2, Server 2003 or later."); // URI prefixes are required, for example // "http://localhost:8080/index/". if (prefixes == null || prefixes.Length == 0) throw new ArgumentException("prefixes"); // A responder method is required if (method == null) throw new ArgumentException("method"); foreach (string s in prefixes) _listener.Prefixes.Add(s); _responderMethod = method; _listener.Start(); } public WebServer(Func method, params string[] prefixes) : this(prefixes, method) { } public void Run() { ThreadPool.QueueUserWorkItem((o) => { Console.WriteLine("Webserver running..."); try { while (_listener.IsListening) { ThreadPool.QueueUserWorkItem((c) => { var ctx = c as HttpListenerContext; try { string rstr = _responderMethod(ctx.Request); System.Diagnostics.Trace.Write(ctx.Request.QueryString); //ctx.Request.QueryString byte[] buf = Encoding.UTF8.GetBytes(rstr); ctx.Response.ContentLength64 = buf.Length; ctx.Response.OutputStream.Write(buf, 0, buf.Length); System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder(); } catch { } // suppress any exceptions finally { // always close the stream ctx.Response.OutputStream.Close(); } }, _listener.GetContext()); } } catch { } // suppress any exceptions }); } public void Stop() { _listener.Stop(); _listener.Close(); } } }
更新2
我尝试了peter解决方案,所以我将WebServer类代码更改为此问题解决方案中的Peter显示。
然后在form1构造函数中我做了:
var ws = new WebServer( () => Task.Run(request => SendResponseAsync(request)), "http://+:8098/"); ws.Run();
然后方法SendResponseAsync:
public async Task SendResponseAsync(HttpListenerRequest request) { string result = ""; string key = request.QueryString.GetKey(0); if (key == "cmd") { if (request.QueryString[0] == "uploadstatus") { switch (Youtube_Uploader.uploadstatus) { case "uploading file": return "uploading " + Youtube_Uploader.fileuploadpercentages; case "status": return Youtube_Uploader.fileuploadpercentages.ToString(); case "file uploaded successfully": Youtube_Uploader.uploadstatus = ""; return "upload completed," + Youtube_Uploader.fileuploadpercentages + "," + Youtube_Uploader.time; default: return "upload unknown state"; } } if (request.QueryString[0] == "nothing") { return "Connection Success"; } if (request.QueryString[0] == "start") { StartRecrod(); result = "Recording started"; } if (request.QueryString[0] == "stop") { dirchanged = false; StartRecrod(); result = "Recording stopped and preparing the file to be shared on youtube"; string fileforupload = await WatchDirectory(); await WaitForUnlockedFile(fileforupload); using (StreamWriter w = new StreamWriter(userVideosDirectory + "\\UploadedVideoFiles.txt", true)) { w.WriteLine(fileforupload); } uploadedFilesList.Add(fileforupload); Youtube_Uploader youtubeupload = new Youtube_Uploader(uploadedFilesList[0]); } } else { result = "Nothing have been done"; } return result; }
WatchDirectory:
private async Task WatchDirectory() { using (FileSystemWatcher watcher = new FileSystemWatcher()) { TaskCompletionSource tcs = new TaskCompletionSource(); watcher.Path = userVideosDirectory; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size; watcher.Filter = "*.mp4"; watcher.Changed += (sender, e) => tcs.SetResult(e.FullPath); watcher.EnableRaisingEvents = true; return await tcs.Task; } }
并持续WaitForUnlockedFile
private async Task WaitForUnlockedFile(string fileName) { while (true) { try { using (IDisposable stream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { /* on success, immediately dispose object */ } break; } catch (IOException) { // ignore exception // NOTE: for best results, consider checking the hresult value of // the exception, to ensure that you are only ignoring the access violation // exception you're expecting, rather than other exceptions, like // FileNotFoundException, etc. which could result in a hung process } // You might want to consider a longer delay...maybe on the order of // a second or two at least. await Task.Delay(100); } }
但是在线上得到错误:
Task.Run严重级代码说明项目文件行错误无法将lambda表达式转换为类型’string []’,因为它不是委托类型Automatic_Record
还有“ http:// +:8098 / ”严重错误代码说明项目文件行错误参数2:无法从’string’转换为’System.Func>
正如回答者 Yuval所说(并且正如我对前一个问题的回答所说),一旦你开始使用async
,它通常必须一直向上传播调用堆栈。 也就是说,还有其他选择:
- 正如我在上一个回答中提到的,您可以同步等待异步操作。 不理想,实际上只是一个权宜之计,直到你可以进一步改进代码,但它可以工作。
- 您可以捕获异步操作(即
Task
)并在以后使用它。
在您的具体示例中,第二个选项应该可以正常工作。 即你需要修复的第一件事就是调整你的构造函数,以便它可以接受异步方法。 然后您可以稍后调用该方法,最好是异步调用。
例如:
class WebServer { private readonly HttpListener _listener = new HttpListener(); private readonly Func> _responderMethod; public WebServer(string[] prefixes, Func> method) { if (!HttpListener.IsSupported) throw new NotSupportedException( "Needs Windows XP SP2, Server 2003 or later."); // URI prefixes are required, for example // "http://localhost:8080/index/". if (prefixes == null || prefixes.Length == 0) throw new ArgumentException("prefixes"); // A responder method is required if (method == null) throw new ArgumentException("method"); foreach (string s in prefixes) _listener.Prefixes.Add(s); _responderMethod = method; _listener.Start(); } public WebServer(Func> method, params string[] prefixes) : this(prefixes, method) { } public void Run() { ThreadPool.QueueUserWorkItem((o) => { Console.WriteLine("Webserver running..."); try { while (_listener.IsListening) { ThreadPool.QueueUserWorkItem(async (c) => { var ctx = c as HttpListenerContext; try { string rstr = await _responderMethod(ctx.Request); System.Diagnostics.Trace.Write(ctx.Request.QueryString); //ctx.Request.QueryString byte[] buf = Encoding.UTF8.GetBytes(rstr); ctx.Response.ContentLength64 = buf.Length; ctx.Response.OutputStream.Write(buf, 0, buf.Length); System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder(); } catch { } // suppress any exceptions finally { // always close the stream ctx.Response.OutputStream.Close(); } }, _listener.GetContext()); } } catch { } // suppress any exceptions }); } public void Stop() { _listener.Stop(); _listener.Close(); } }
当然,使用更改的构造函数,还必须更改任何其他调用方(如果存在)。 理想情况下,您可以更改与这些调用者相关的代码,以便它也遵循async
模型,充分利用并获得该方法的全部好处。
但同样,如果您不能或不会这样做,则可以将旧的同步代码调整为异步模型。 例如,如果您有这样的事情:
var server = new WebServer(SomeOtherSendResponse, "http://+:8098/");
…你可以把它改成这样的东西:
var server = new WebServer( request => Task.Run(() => SomeOtherSendResponse(request)), "http://+:8098/");
您也可以创建一个构造函数重载来包装任何此类调用者的同步方法,以便原始调用站点可以保持不变:
public WebServer(Func method, params string[] prefixes) : this(request => Task.Run(() => method(request)), prefixes) { }
请注意,通过在调用堆栈中一直跟随async
,代码中没有操作必须在某个异步事件或操作上等待一段延长时间的位置将导致代码阻塞。 相反,它们只是将控制权返回给执行线程,允许框架在异步事件或操作发生或完成时以后继续执行async
方法。
如果你试图通过简单地等待异步操作完成来“解决”问题,你会(在这种特殊情况下)绑定一个线程池线程,阻塞它直到它可以继续。 这样,一旦异步操作启动,线程池线程就会返回到池中,并且程序将不再需要为该操作再次使用线程池线程,直到操作本身实际完成为止。
顺便说一句:作为一般规则,不建议使用代码来忽略可能发生的每个exception。 您应该只捕获那些您希望发生的exception以及您确定可以安全忽略的exception。 即便如此,您至少应该以某种方式报告它们以帮助您或者用户能够诊断代码中不存在的问题。 其他exception很容易使程序的执行处于损坏状态。 充其量,这些问题将非常难以诊断和修复,最坏的情况是最终可能会破坏用户状态,输出数据等。
问题是,是否有可能使SendResponse返回字符串,因为我需要它并使用await?
异步是“一直”。 这意味着一旦开始在代码中使用await
,您的方法签名就会向上传播,遍历整个调用堆栈。 这意味着代码中的任何async
方法都必须返回Task
或Task
并添加async
修饰符,以便编译器检测到这是异步方法并需要进行转换进入状态机。
这意味着这个同步签名:
public string SendResponse(HttpListenerRequest request)
需要变成异步签名:
public async Task SendResponseAync(HttpListenerRequest request)
可以使用Task.Result
同步阻止代码。 我不推荐它,因为你不应该阻止异步代码 。 这只会带来麻烦(通常是死锁)。
将SendResponse
声明为Task
。 这表示此Task将返回一个字符串。
public async Task SendResponse(HttpListenerRequest request) { ... }
无论何时你打电话:
string result = await SendRespone(request);
我相信您最新的编译错误是由于没有为WebServer类提供具有正确签名的lambda。 因此,您需要更改传递构造函数的内容或预期内容。 通过实现WebServer的方式,它需要一个非响应的_responderMethod方法。 因此,尝试使SendResponse()异步会产生反作用。 我强烈建议重构WebServer以异步处理响应,或者返回使SendResponse()非异步。
如果你想让你的代码编译成你可以尝试这样的东西:
var ws = new WebServer( request => { var t = SendResponseAsync(request); t.Wait(); return t.Result; }, "http://+:8098/"); ws.Run();
您可能还必须将lambda包装在正确的委托类型中,如下所示:
var ws = new WebServer( new Func(request => { var t = SendResponseAsync(request); t.Wait(); return t.Result; }), "http://+:8098/"); ws.Run();