为什么在第一次输出后控制台冻结输出?
Why output on console freeze after first output?
这里我有用 C# 编写的简单代码。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Reactive.Subjects;
namespace ReactiveProgramming
{
class Program
{
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// it works if I remove below two lines ...
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r");
});
generateSeq.Run();
}
}
class GenerateSequence
{
public Subject<int> Sequence = new Subject<int>();
public void Run(int runTimes = 10)
{
ConsoleKeyInfo cki;
Task.Run(() => runForTimes(10));
do
{
cki = Console.ReadKey();
} while (cki.Key != ConsoleKey.Escape);
}
public void runForTimes(int runTimes = 10)
{
for (var i = 0; i < 10; i++)
{
Sequence.OnNext(i);
Thread.Sleep(1000);
}
}
}
}
但是它不是在彼此之上打印序列,而是在第一次发出后冻结输出。
也在 Linux 中进行了测试...同样的输出。
如果我将这些行 Console.SetCursorPosition
和 Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r")
从 subscribe 中远程...它可以工作并在屏幕上一个接一个地打印所有数字,但我想一个接一个地打印...
但是如果我像这样更改我的 Main 函数:
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// Console.SetCursorPosition(0, Console.CursorTop - 1);
// Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r");
});
generateSeq.Run();
}
我注释掉这两行的地方...输出如下...
但是我不想像第二张图片那样按顺序输出,我想在同一个位置打印输出。将新输出覆盖在旧输出上
注意:我在 Macbook Pro (Big Sur) 上 运行,它发生在 .net core 3.1 或 .net 5.0 并使用 iTerm 作为控制台模拟器
SetCursorPosition 在不从另一个线程调用时工作得很好。您可以使用异步方法来解决问题,而不是使用 Task.Run
class Program
{
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// move cursor back to previous line
Console.SetCursorPosition(0 ,Console.CursorTop - 1);
});
// start background operation
generateSeq.Run();
}
}
class GenerateSequence
{
public readonly Subject<int> Sequence = new();
public void Run(int runTimes = 10)
{
ConsoleKeyInfo cki;
// create a cancelation token, because if the user presses
// Escape key we don't need to run our background task
// anymore and the task should be stopped.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
// we can not use await keyword here because we need to
// listen to ReadKey functions in case the user wants to
// stop the execution. without the await, task will run in
// the background asynchronously
var task = RunForTimes(runTimes,token);
// wait for the Escape key to cancel the execution or stop it
// if it's already running
do
{
cki = Console.ReadKey();
} while (cki.Key != ConsoleKey.Escape && !task.IsCompleted);
// cancel the background task if it's not compeleted.
if (!task.IsCompleted)
tokenSource.Cancel();
// Revert CursorPosition to the original state
Console.SetCursorPosition(0, Console.CursorTop + 1);
Console.WriteLine("Execution ends");
}
// we use an async task instead of a void to run our background
// job Asynchronously.
// the main difference is, we should not use a separate thread
// because we need to be on the main thread to safely access the Console (to read or write)
private async Task RunForTimes(int runTimes, CancellationToken token)
{
for (var i = 0; i < runTimes; i++)
{
Sequence.OnNext(i);
await Task.Delay(1000, token);
// exit the operation if it is requested
if (token.IsCancellationRequested) return;
}
}
}
如果我正在写这篇文章,我会选择这个实现:
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
IObservable<System.ConsoleKeyInfo> keys =
Observable
.Start(() => Console.ReadKey());
await
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Take(10)
.TakeUntil(keys)
.Do(x =>
{
Console.WriteLine(x);
Console.SetCursorPosition(0, Console.CursorTop - 1);
},
() => Console.SetCursorPosition(0, Console.CursorTop + 1));
Console.WriteLine("Bye World!");
}
尽可能避免使用主题。
这里我有用 C# 编写的简单代码。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Reactive.Subjects;
namespace ReactiveProgramming
{
class Program
{
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// it works if I remove below two lines ...
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r");
});
generateSeq.Run();
}
}
class GenerateSequence
{
public Subject<int> Sequence = new Subject<int>();
public void Run(int runTimes = 10)
{
ConsoleKeyInfo cki;
Task.Run(() => runForTimes(10));
do
{
cki = Console.ReadKey();
} while (cki.Key != ConsoleKey.Escape);
}
public void runForTimes(int runTimes = 10)
{
for (var i = 0; i < 10; i++)
{
Sequence.OnNext(i);
Thread.Sleep(1000);
}
}
}
}
但是它不是在彼此之上打印序列,而是在第一次发出后冻结输出。
也在 Linux 中进行了测试...同样的输出。
如果我将这些行 Console.SetCursorPosition
和 Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r")
从 subscribe 中远程...它可以工作并在屏幕上一个接一个地打印所有数字,但我想一个接一个地打印...
但是如果我像这样更改我的 Main 函数:
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// Console.SetCursorPosition(0, Console.CursorTop - 1);
// Console.Write("\r" + new string(' ', Console.WindowWidth) + "\r");
});
generateSeq.Run();
}
我注释掉这两行的地方...输出如下...
但是我不想像第二张图片那样按顺序输出,我想在同一个位置打印输出。将新输出覆盖在旧输出上
注意:我在 Macbook Pro (Big Sur) 上 运行,它发生在 .net core 3.1 或 .net 5.0 并使用 iTerm 作为控制台模拟器
SetCursorPosition 在不从另一个线程调用时工作得很好。您可以使用异步方法来解决问题,而不是使用 Task.Run
class Program
{
static void Main(string[] args)
{
var generateSeq = new GenerateSequence();
Console.WriteLine("Hello World!");
generateSeq.Sequence.Subscribe(val =>
{
Console.WriteLine(val);
// move cursor back to previous line
Console.SetCursorPosition(0 ,Console.CursorTop - 1);
});
// start background operation
generateSeq.Run();
}
}
class GenerateSequence
{
public readonly Subject<int> Sequence = new();
public void Run(int runTimes = 10)
{
ConsoleKeyInfo cki;
// create a cancelation token, because if the user presses
// Escape key we don't need to run our background task
// anymore and the task should be stopped.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
// we can not use await keyword here because we need to
// listen to ReadKey functions in case the user wants to
// stop the execution. without the await, task will run in
// the background asynchronously
var task = RunForTimes(runTimes,token);
// wait for the Escape key to cancel the execution or stop it
// if it's already running
do
{
cki = Console.ReadKey();
} while (cki.Key != ConsoleKey.Escape && !task.IsCompleted);
// cancel the background task if it's not compeleted.
if (!task.IsCompleted)
tokenSource.Cancel();
// Revert CursorPosition to the original state
Console.SetCursorPosition(0, Console.CursorTop + 1);
Console.WriteLine("Execution ends");
}
// we use an async task instead of a void to run our background
// job Asynchronously.
// the main difference is, we should not use a separate thread
// because we need to be on the main thread to safely access the Console (to read or write)
private async Task RunForTimes(int runTimes, CancellationToken token)
{
for (var i = 0; i < runTimes; i++)
{
Sequence.OnNext(i);
await Task.Delay(1000, token);
// exit the operation if it is requested
if (token.IsCancellationRequested) return;
}
}
}
如果我正在写这篇文章,我会选择这个实现:
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
IObservable<System.ConsoleKeyInfo> keys =
Observable
.Start(() => Console.ReadKey());
await
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Take(10)
.TakeUntil(keys)
.Do(x =>
{
Console.WriteLine(x);
Console.SetCursorPosition(0, Console.CursorTop - 1);
},
() => Console.SetCursorPosition(0, Console.CursorTop + 1));
Console.WriteLine("Bye World!");
}
尽可能避免使用主题。