我有一个网络服务器,我添加了这个方法:
public async Task<string> SendResponseAsync(HttpListenerRequest request)//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 = "";
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);
uploadedFilesList.Add(fileforupload);
Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);//uploadedFilesList[0]);
}
}
else
{
result = "Nothing have been done";
}
return result;
}
问题出在这一部分:
result = "Recording stopped and preparing the file to be shared on youtube";
string fileforupload = await WatchDirectory();
await WaitForUnlockedFile(fileforupload);
问题是它不会返回结果,直到它完成等待。 但我需要以某种方式确定它将首先返回结果,之后它将完成其余的工作。
这是WatchDirectory方法:
private async Task<string> WatchDirectory()
{
using (FileSystemWatcher watcher = new FileSystemWatcher())
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
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)
{
}
await Task.Delay(100);
}
}
更新:
这就是我在form1构造函数中使用方法SendResponseAsync为WebServer创建实例的方法。
var ws = new WebServer(
request => Task.Run(() => SendResponseAsync(request)),
"http://+:8098/");
这是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<HttpListenerRequest, Task<string>> _responderMethod;
public WebServer(string[] prefixes, Func<HttpListenerRequest, Task<string>> 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<HttpListenerRequest, Task<string>> 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 (Exception error)
{
string ttt = error.ToString();
} // 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();
}
}
答案 0 :(得分:1)
如果我正确理解了这个问题,那么在SendResponseAsync()
方法的操作过程中会有多个“检查点”,并且每个方法都希望能够向客户端发送带有某些状态消息的响应(例如,想要在"Recording stopped and preparing the file to be shared on youtube"
的调用完成后,但在执行对WatchDirectory()
的调用之前)报告结果WaitForUnlockedFile()
。
基本问题是SendResponseAsync()
方法可以像任何其他方法async
一样,只返回单个值。你不能让任何方法多次返回†,这对任何其他类型的async
方法都是正确的。
那么,该怎么办?好吧,如果没有在上下文中看到代码(即没有a good, minimal, complete code example可靠地再现您的问题),就不可能肯定地说。但是C#中的一个惯用方法是使用IProgress<T>
接口,允许使用状态值回调方法的调用者,以便它可以适当地处理它们(例如通过将它们发送到客户端)。
例如,您可以将方法更改为:
public async Task SendResponseAsync(
HttpListenerRequest request, IProgress<string> progress)
{
string key = request.QueryString.GetKey(0);
if (key == "cmd")
{
if (request.QueryString[0] == "uploadstatus")
{
switch (Youtube_Uploader.uploadstatus)
{
case "uploading file":
progress.Report("uploading " + Youtube_Uploader.fileuploadpercentages);
return;
case "status":
progress.Report(Youtube_Uploader.fileuploadpercentages.ToString());
return;
case "file uploaded successfully":
Youtube_Uploader.uploadstatus = "";
Youtube_Uploader.fileuploadpercentages + ","
+ Youtube_Uploader.time;
default:
progress.Report("upload unknown state");
return;
}
}
if (request.QueryString[0] == "nothing")
{
progress.Report("Connection Success");
return;
}
if (request.QueryString[0] == "start")
{
StartRecrod();
progress.Report("Recording started");
}
if (request.QueryString[0] == "stop")
{
dirchanged = false;
StartRecrod();
string fileforupload = await WatchDirectory();
progress.Report("Recording stopped and preparing the file to be shared on youtube");
await WaitForUnlockedFile(fileforupload);
uploadedFilesList.Add(fileforupload);
Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);//uploadedFilesList[0]);
}
}
else
{
progress.Report("Nothing have been done");
}
}
假设代码看起来像这样:
string result = await SendResponseAsync(request);
SendResultToClient(result);
然后你可以将方法的新版本称为:
await SendResponseAsync(request, new Progress<string>(s => SendResultToClient(s)));
这将创建一个Progress<T>
(一个实现IProgress<T>
的内置类)的新实例,它将调用您的SendResultToClient()
方法将状态消息发送给客户端。
上面的变体是允许方法仍然返回一个结果,并且只对那些需要在方法完成之前返回的结果使用progress
参数。在这种情况下,我将return ...some string value...;
更改为progress.Report(...some string value...);
的所有地方都会恢复原来的代码,您仍然会将该方法声明为async Task<string>
,然后返回result
值,当然使用该值在完成等待的SendResponseAsync()
调用时将结果发送给客户端。即除了添加IProgress<T>
参数外,呼叫站点不会改变。
修改强>
要解决问题中添加的信息:
WebServer
构造函数。几周之前,您使用的WebServer
代码与the code I helped someone else完全相同。在那个问题中,目标是能够将async
方法传递给构造函数。使用Task.Run()
的示例仅用于使用WebServer
但没有async
的某些其他来电者的情况通过的方法。在任务中包含对async
方法的调用是完全错误的,不会产生所需的结果。 (实际上,你发布的代码甚至不应该编译,因为Task.Run()
正在返回Task<Task<string>>
,而委托应该只返回Task<string>
。假设它确实编译,假设有一些其他 hack未显示可解决该编译时错误。)async
方法需要传递用于报告状态的相应IProgress<T>
实例。唯一的方法是更改最终用于调用该方法的委托的签名,然后稍后传递所需的IProgress<T>
实例。我将提供一个示例,说明下面的变化可能是什么...... 首先,您的WebServer
类需要更改,以接受不同的方法签名:
class WebServer
{
private readonly HttpListener _listener = new HttpListener();
private readonly Func<HttpListenerRequest, IProgress<string>, Task> _responderMethod;
public WebServer(string[] prefixes, Func<HttpListenerRequest, IProgress<string>, Task> 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<HttpListenerRequest, IProgress<string>, Task> 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
{
await _responderMethod(ctx.Request, new Progress<string>(rstr =>
{
byte[] buf = Encoding.UTF8.GetBytes(rstr);
ctx.Response.ContentLength64 = buf.Length;
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
});
System.Diagnostics.Trace.Write(ctx.Request.QueryString);
//ctx.Request.QueryString
System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder();
}
catch (Exception error)
{
string ttt = error.ToString();
} // 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();
}
}
然后构造函数调用应该如下所示:
var ws = new WebServer(SendResponseAsync, "http://+:8098/");
当然,您已经修改了SendResponseAsync()
,如上所述,以便它可以接收IProgress<T>
的实例作为参数。
完成所有这些后,WebServer
类的Run()
方法中将结果文本写入响应输出流的代码块封装在用作Action<string>
的匿名方法中传递给Progress<string>
委托调用的_responseMethod
实例的委托。
注意:强>
在您的方案中,上述内容可能正确也可能不正确。同样,如果没有一个好的代码示例,就不可能确切地知道。但请务必记住,Progress<T>
类在创建时使用当前SynchronizationContext
来提升其ProgressChanged
事件。在许多情况下,这正是您想要的。但是根据这里调用的上下文,你最终可能会使用线程池来引发事件,从而引入了无序回调的可能性。这当然会导致数据以错误的顺序出现在响应流中。
因此,作为使用Progress<T>
的替代方法,您可能希望使用同步引发事件的自定义类。例如:
class SynchronousProgress<T> : IProgress<T>
{
public event EventHandler<T> ProgressChanged;
public SynchronousProgress() { }
public SynchronousProgress(Action<T> callback)
{
ProgressChanged = (sender, e) => callback(e);
}
public void Report(T t)
{
EventHandler<T> handler = ProgressChanged;
if (handler != null)
{
handler(this, t);
}
}
}
要使用它,只需将Progress
替换为上面SynchronousProgress
方法示例中的Run()
。
† - 当然忽略迭代器方法。它们具有不同的语义,允许yield return
执行多次。但它们也不能与async
一起使用,因此在这里不相关。