我有一个自定义小部件,必须启动
列表
计划的
Task
对象,为了简单起见,让我们以Xamarin的文本到语音转换示例为例。
现在我想安排一个演讲,等五秒钟,然后再开始另一个。唯一的问题是我不知道怎么做。此外,我必须能够一次全部取消它们。
迭代1:
Task.ContinueWith
编辑
:根据建议,我正在使用
任务.继续
使用单个取消令牌:
public CancellationTokenSource cancel_source;
public CancellationToken cancel_token;
public async void Play_Clicked(object sender, System.EventArgs e)
{
if (!is_playing)
{
System.Diagnostics.Debug.Print("start");
is_playing = true;
cancel_source = new CancellationTokenSource();
cancel_token = cancel_source.Token;
current_task =
Task.Factory.StartNew(
async () =>
{
System.Diagnostics.Debug.Print("first task");
await DependencyService.Get<ITextToSpeech>().SpeakAsync("Wait for five seconds...", cancel_source, cancel_token);
}
).ContinueWith(
async (arg) =>
{
System.Diagnostics.Debug.Print("wait task");
await Task.Delay(5000, cancel_token);
}
).ContinueWith(
async (arg) =>
{
System.Diagnostics.Debug.Print("last task");
await DependencyService.Get<ITextToSpeech>().SpeakAsync("You waited!", cancel_source, cancel_token);
}
).ContinueWith(
async (arg) =>
{
System.Diagnostics.Debug.Print("All done!");
await Task.Delay(100);
}
);
await current_task;
}
else
{
System.Diagnostics.Debug.Print("stop");
//foreach (var p in l) <----------------- will bother about canceling next, not right now
//{
// if (p.task.IsCompleted) continue;
// DependencyService.Get<ITextToSpeech>().CancelSpeak();
// p.source.Cancel();
//}
is_playing = false;
//DependencyService.Get<ITextToSpeech>().CancelSpeak();
//cancel_source.Cancel();
//cancel_source = null;
//current_task = null;
}
}
我实现的很奇怪,当我单击按钮时,它只会说“等5秒钟”,当我再次单击时,它会说第二部分。
我的实施如下:
public class TextToSpeechImplementation : ITextToSpeech
{
public AVSpeechSynthesizer speechSynthesizer;
public AVSpeechUtterance speechUtterance;
public TaskCompletionSource<bool> tcsUtterance;
public CancellationTokenSource cancel_source;
public CancellationToken cancel_token;
public async Task SpeakAsync(string text, CancellationTokenSource source, CancellationToken token)
{
cancel_source = source;
cancel_token = token;
tcsUtterance = new TaskCompletionSource<bool>();
System.Diagnostics.Debug.Print("START ASYNC IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
var now = System.DateTime.Now;
speechSynthesizer = new AVSpeechSynthesizer();
speechUtterance = new AVSpeechUtterance(text);
speechSynthesizer.DidFinishSpeechUtterance += (sender, e) => System.Diagnostics.Debug.Print("STOP ASYNC IMPLEMENTATION {0} duration {1}", System.DateTime.Now.ToString("HH:mm:ss"),
(System.DateTime.Now - now).TotalSeconds);
speechSynthesizer.DidCancelSpeechUtterance += (sender, e) => System.Diagnostics.Debug.Print("SPEECH CANCELED");
speechSynthesizer.SpeakUtterance(speechUtterance);
await tcsUtterance.Task;
}
public void CancelSpeak()
{
speechSynthesizer.StopSpeaking(AVSpeechBoundary.Immediate);
tcsUtterance.TrySetResult(true);
cancel_source.Cancel();
}
}
我看到调度的任务几乎是同时运行的,所以我得到的只是“等待5秒钟”,然后就没有其他内容(很明显,任务已经全部完成运行)。
有什么提示吗?
迭代2:生成任务
多亏了瑞安·皮尔斯·威廉姆斯,我已经修改了课程,现在唯一真正的问题是如何
取消
即将开始/当前任务的列表。
现在,工作负载的接口创建了一个新的文本到语音类实例,该实例取自Xamarin的教程(我仍然想简单地播放!),如下:
public interface ITextToSpeech
{
ITextToSpeech New(string text, CancellationTokenSource source, CancellationToken token);
void Speak(string text);
Task SpeakAsync(string text);
void CancelSpeak();
}
public class TextToSpeechImplementation : ITextToSpeech
{
public string speech_text;
public AVSpeechSynthesizer speechSynthesizer;
public AVSpeechUtterance speechUtterance;
public TaskCompletionSource<bool> tcsUtterance;
public CancellationTokenSource cancel_source;
public CancellationToken cancel_token;
public ITextToSpeech New(string text, CancellationTokenSource source, CancellationToken token)
{
speech_text = text;
cancel_source = source;
cancel_token = token;
speechSynthesizer = new AVSpeechSynthesizer();
speechUtterance = new AVSpeechUtterance(speech_text);
speechSynthesizer.DidFinishSpeechUtterance += (sender, e) => System.Diagnostics.Debug.Print("STOP IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
speechSynthesizer.DidCancelSpeechUtterance += (sender, e) => System.Diagnostics.Debug.Print("SPEECH CANCELED");
return this;
}
public void Speak(string text)
{
System.Diagnostics.Debug.Print("START IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
speechSynthesizer.SpeakUtterance(speechUtterance);
}
public async Task SpeakAsync(string text)
{
System.Diagnostics.Debug.Print("START ASYNC IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
tcsUtterance = new TaskCompletionSource<bool>();
speechSynthesizer.SpeakUtterance(speechUtterance);
await tcsUtterance.Task;
}
public void CancelSpeak()
{
speechSynthesizer.StopSpeaking(AVSpeechBoundary.Immediate);
tcsUtterance?.TrySetResult(true);
cancel_source.Cancel();
}
}
小部件类现在使用
仅同步调用
为了工作量,因为我正在生成我认为不需要的任务
async
那里:
public bool is_playing;
public CancellationTokenSource cancel_source;
public CancellationToken cancel_token;
public List<string> l;
public PlayerWidget(int category, int book)
{
is_playing = false;
l = new List<string>();
cancel_source = new CancellationTokenSource();
cancel_token = cancel_source.Token;
}
public void Play_Clicked(object sender, System.EventArgs e)
{
if (!is_playing)
{
System.Diagnostics.Debug.Print("start");
is_playing = true;
l.Clear();
l.Add("Wait for five seconds...");
l.Add("You waited!");
l.Add("and the last one is here for you.");
l.Add("Just kidding, my man, you have this last sentence here and shall be perfectly said. Now I have to go... so... farewell!");
var state = new TaskState()
{
Delay = 1000,
CancellationToken = cancel_token,
Workload = DependencyService.Get<ITextToSpeech>().New(l[0], cancel_source, cancel_token)
};
Task.Factory.StartNew(TaskExecutor, state, cancel_token).ContinueWith(TaskComplete);
}
else
{
// THIS DOES NOT WORK
System.Diagnostics.Debug.Print("stop");
is_playing = false;
cancel_source.Cancel();
}
}
public void TaskExecutor(object obj)
{
var state = (TaskState)obj;
System.Diagnostics.Debug.Print("Delaying execution of Task {0} for {1} [ms] at {2}", state.TaskId, state.Delay, System.DateTime.Now.ToString("HH:mm:ss"));
state.CancellationToken.ThrowIfCancellationRequested();
// Delay execution, while monitoring for cancellation
// If Task.Delay isn't responsive enough, use something like this.
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < state.Delay)
{
Thread.Yield(); // don't hog the CPU
state.CancellationToken.ThrowIfCancellationRequested();
}
System.Diagnostics.Debug.Print("Beginning to process workload of Task {0} '{1}' at {2}", state.TaskId, l[state.TaskId], System.DateTime.Now.ToString("HH:mm:ss"));
state.Workload.Speak(l[state.TaskId]);
}
void TaskComplete(Task parent)
{
var state = (TaskState)parent.AsyncState;
try
{
parent.Wait();
System.Diagnostics.Debug.Print("Task {0} successfully completed processing its workload without error at {1}", state.TaskId, System.DateTime.Now.ToString("HH:mm:ss"));
}
catch (TaskCanceledException)
{
System.Diagnostics.Debug.Print("The Task {0} was successfully cancelled at {1}", parent.AsyncState, System.DateTime.Now.ToString("HH:mm:ss"));
// since it was cancelled, just return. No need to continue spawning new tasks.
return;
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print("An unexpected exception brought Task {0} down. {1} at {2}", state.TaskId, ex.Message, System.DateTime.Now.ToString("HH:mm:ss"));
}
if (state.TaskId == l.Count - 1)
{
is_playing = false;
}
else
{
// Kick off another task...
var child_state = new TaskState()
{
Delay = 5000,
CancellationToken = cancel_token,
Workload = DependencyService.Get<ITextToSpeech>().New(l[state.TaskId + 1], cancel_source, cancel_token)
};
Task.Factory.StartNew(TaskExecutor, child_state, cancel_token).ContinueWith(TaskComplete);
}
}
现在它像一个符咒一样工作,它正确地调度,并且工作负载得到执行。很好。
现在的问题是:
如何取消任务
?我需要
停止
当前正在播放的TTS,并阻止创建任何其他任务。我想是打电话给
cancel_source.Cancel();
这就足够了,但正如您从日志中看到的那样:
start
Delaying execution of Task 0 for 1000 [ms] at 10:21:16
Beginning to process workload of Task 0 'Wait for five seconds...' at 10:21:17
START IMPLEMENTATION 10:21:17
Task 0 successfully completed processing its workload without error at 10:21:17
Delaying execution of Task 1 for 5000 [ms] at 10:21:17
2018-10-24 10:21:17.565591+0200 TestTasks.iOS[71015:16136232] SecTaskLoadEntitlements failed error=22 cs_flags=200, pid=71015
2018-10-24 10:21:17.565896+0200 TestTasks.iOS[71015:16136232] SecTaskCopyDebugDescription: TestTasks.iOS[71015]/0#-1 LF=0
STOP IMPLEMENTATION 10:21:19
Beginning to process workload of Task 1 'You waited!' at 10:21:22
START IMPLEMENTATION 10:21:22
Task 1 successfully completed processing its workload without error at 10:21:22
Delaying execution of Task 2 for 5000 [ms] at 10:21:22
Thread started: <Thread Pool> #6
STOP IMPLEMENTATION 10:21:23
Beginning to process workload of Task 2 'and the last one is here for you.' at 10:21:27
START IMPLEMENTATION 10:21:27
Task 2 successfully completed processing its workload without error at 10:21:27
Delaying execution of Task 3 for 5000 [ms] at 10:21:27
stop
An unexpected exception brought Task 3 down. One or more errors occurred. at 10:21:27
STOP IMPLEMENTATION 10:21:29
start
An unexpected exception brought Task 4 down. One or more errors occurred. at 10:21:34
stop
start
An unexpected exception brought Task 6 down. One or more errors occurred. at 10:21:39
我的简单而天真的代码
不停止当前播放的文本
立即继续,直到完成TTS,并停止所有其他任务的生成。但是如果我再次单击播放按钮,任务就不会再开始了,正如你所看到的,我在生成新任务时有一些奇怪的错误(对我来说)。
我又是新来的,我能做什么?
迭代3:单个任务,多个令牌
像往常一样,Ryan的建议非常有用,现在我已经成功地编写了一个非常基本的任务处理程序,几乎可以工作:
public void Play_Clicked(object sender, System.EventArgs e)
{
l.Clear();
l.Add("Wait for five seconds...");
l.Add("You waited!");
l.Add("and the last one is here for you.");
l.Add("Just kidding, my man, you have this last sentence here and shall be perfectly said. Now I have to go... so... farewell!");
System.Diagnostics.Debug.Print("click handler playing {0}", is_playing);
try
{
if (!is_playing)
{
System.Diagnostics.Debug.Print("start");
cancel_source = new CancellationTokenSource();
cancel_token = cancel_source.Token;
current_task = new Task(SingleTask, cancel_token);
current_task.Start();
is_playing = true;
}
else
{
System.Diagnostics.Debug.Print("stop");
is_playing = false;
cancel_token.ThrowIfCancellationRequested();
cancel_source.Cancel();
cancel_token.ThrowIfCancellationRequested();
current_speaker.CancelSpeak();
cancel_token.ThrowIfCancellationRequested();
}
}
catch(Exception)
{
System.Diagnostics.Debug.Print("cancel");
cancel_source.Cancel();
current_speaker.CancelSpeak();
is_playing = false;
}
}
处理程序定义如下:
public void SingleTask()
{
System.Diagnostics.Debug.Print("Single task started at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
foreach(var p in l)
{
System.Diagnostics.Debug.Print("Waiting 5s");
//cancel_token.ThrowIfCancellationRequested();
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < 5000)
{
Thread.Yield(); // don't hog the CPU
//cancel_token.ThrowIfCancellationRequested();
}
current_speaker = DependencyService.Get<ITextToSpeech>().New(p, cancel_source, cancel_token);
try
{
System.Diagnostics.Debug.Print("Single task speaking at {0} sentence '{1}'", System.DateTime.Now.ToString("HH:mm:ss"), p);
current_speaker.Speak(p);
while (current_speaker.IsPlaying())
{
Thread.Yield();
}
}
catch (Exception)
{
System.Diagnostics.Debug.Print("Single task CANCELING at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
cancel_source.Cancel();
current_speaker.CancelSpeak();
}
}
System.Diagnostics.Debug.Print("Single task FINISHED at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
is_playing = false;
}
现在,任务被调度、执行和多次工作。问题是现在
取消
它。
什么有效
:当TTS在句子中间时终止任务。它奇怪地同时调用“stop”和“cancel”,但它工作:
click handler playing True
stop
cancel
2018-10-29 12:35:37.534358+0100[85164:17740514] [AXTTSCommon] _BeginSpeaking: couldn't begin playback
SPEECH CANCELED
什么不起作用
:等待下一个短语时终止任务。在等待期间,它会再次调用“stop”和“cancel”,但正如您所看到的,它会继续
下一个
句子,然后按我的意愿停止(再次单击按钮时,它会正确地重新开始)。
click handler playing False
start
Single task started at 12:36:56
Waiting 5s
Single task speaking at 12:37:01 sentence 'Wait for five seconds...'
START IMPLEMENTATION 12:37:01
STOP IMPLEMENTATION 12:37:02
Waiting 5s
Thread finished: <Thread Pool> #34
Thread started: <Thread Pool> #37
click handler playing True
stop
cancel
Single task speaking at 12:37:07 sentence 'You waited!'
START IMPLEMENTATION 12:37:07
STOP IMPLEMENTATION 12:37:08
我真的相信我错过了一小块!
最终解决方案
这是Ryan建议的最后一个代码,它现在可以工作了,它可以在句子中间停止演讲,在等待时停止任务,我所需要的一切。对于子孙后代来说,棘手的部分是在这里混合了任务和本机任务(TTS依赖服务),但现在我认为它更清晰:
public void Play_Clicked(object sender, System.EventArgs e)
{
l.Clear();
l.Add("Wait for five seconds...");
l.Add("You waited!");
l.Add("and the last one is here for you.");
l.Add("Just kidding, my man, you have this last sentence here and shall be perfectly said. Now I have to go... so... farewell!");
System.Diagnostics.Debug.Print("click handler playing {0}", is_playing);
if (!is_playing)
{
System.Diagnostics.Debug.Print("start");
cancel_source = new CancellationTokenSource();
cancel_token = cancel_source.Token;
current_task = new Task(SingleTask, cancel_token);
current_task.Start();
is_playing = true;
}
else
{
System.Diagnostics.Debug.Print("stop");
is_playing = false;
cancel_source.Cancel();
current_speaker.CancelSpeak();
}
}
public void SingleTask()
{
System.Diagnostics.Debug.Print("Single task started at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
foreach(var p in l)
{
System.Diagnostics.Debug.Print("Waiting 5s");
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < 5000)
{
Thread.Yield(); // don't hog the CPU
if (cancel_source.IsCancellationRequested)
{
cancel_source.Cancel();
current_speaker.CancelSpeak();
return;
}
}
current_speaker = DependencyService.Get<ITextToSpeech>().New(p, cancel_source, cancel_token);
try
{
System.Diagnostics.Debug.Print("Single task speaking at {0} sentence '{1}'", System.DateTime.Now.ToString("HH:mm:ss"), p);
current_speaker.Speak(p);
while (current_speaker.IsPlaying())
{
Thread.Yield();
}
}
catch (Exception)
{
System.Diagnostics.Debug.Print("Single task CANCELING at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
cancel_source.Cancel();
current_speaker.CancelSpeak();
}
}
System.Diagnostics.Debug.Print("Single task FINISHED at {0}", System.DateTime.Now.ToString("HH:mm:ss"));
is_playing = false;
}