background task not waiting for file writes - c#

I have a long-running background task which is just a while loop constantly iterating over a BlockingCollection to log messages to a file. At first i used a foreach loop which would create a streamwriter and write the line asynchronously.
public static BlockingCollection<LogMessage> LogMessages = new BlockingCollection<LogMessage>();
private static async void RunTask()
{
while (true)
{
var allMessages = LogMessages.GetConsumingEnumerable();
foreach (var message in allMessages)
{
using (StreamWriter sr = new StreamWriter(File.Open(DebugPath, FileMode.Append)))
{
await sr.WriteLineAsync(message.Text);
}
}
}
}
The code above worked well, but it didn't seem like the best way utilize the streamwriter, so I tried switching the foreach and using statements. This caused the debug file to be created, but the file would stay at 0kb and never save. Also I couldn't open the file because it was in use while the application was open.
using (StreamWriter sr = new StreamWriter(File.Open(DebugPath, FileMode.Append)))
{
foreach (var message in allMessages)
{
// I suspect it's not waiting for this to finish.
await sr.WriteLineAsync(message.Text);
}
}
Is this the expected behavior of the background task? What is the correct way to do this?

You can run a separate task in which to synchronously write messages to a file:
public async void RunTask()
{
await DoTask();
}
public Task DoTask()
{
return Task.Run(() =>
{
using (StreamWriter sr = new StreamWriter(File.Open(DebugPath, FileMode.Append)))
{
foreach (var message in allMessages)
{
sr.WriteLine(message);
}
}
});
}

Related

Reading large txt file async and reporting progress in progressbar WPF C# [duplicate]

This question already has answers here:
Read/Write text file progressbar in C#
(1 answer)
How to show progress of reading from a file and writing to a database
(1 answer)
Closed 1 year ago.
I am trying to read a large txt file (>50MB) asynchronously and while it is being read, report the progress on the UI progressbar and have the option to cancel the process. So far I have read and processed the file async as I wanted but I could not solve the progressbar part.
public static async Task<string> ReadTxtAsync(string filePath)
{
try
{
using (var reader = File.OpenText(filePath))
{
var content = await reader.ReadToEndAsync();
return content;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return null;
}
}
public static async Task<Dictionary<string, int>> OpenTxtAsync()
{
Dictionary<string, int> uniqueWords = new Dictionary<string, int>();
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Text Documents (*.txt)|*.txt";
string content = null;
try
{
if (openFileDialog.ShowDialog() == true)
{
string filePath = openFileDialog.FileName.ToString();
if (openFileDialog.CheckFileExists && new[] { ".txt" }.Contains(Path.GetExtension(filePath).ToLower()) && filePath != null)
{
Task<string> readText = ReadTxtAsync(filePath);
content = await readText;
uniqueWords = WordExtractor.CountWords(ref content);
}
else MessageBox.Show("Please use .txt format extension!");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return uniqueWords;
}
private async void LoadFileButtonClick(object sender, RoutedEventArgs e)
{
Task<Dictionary<string, int>> dictionaryContent = TextFileLoader.OpenTxtAsync();
uniqueWords = await dictionaryContent;
UpdateListView();
}
How can I check where ReadToEndAsync() is currently? How can I get it to continously update the progressbar and how can I cancel it?
EDIT:
Thanks to #emoacht I managed to get the progressbar to update correctly and display its percentage. The only thing that remains is to cancel the task, which I tried according to a Tim Corey video, but it did not work on my code.
public static async Task<string> ReadTextAsync(string filePath, IProgress<(double current, double total)> progress, CancellationToken cancellationToken)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
var readTask = reader.ReadToEndAsync();
cancellationToken.ThrowIfCancellationRequested();
var progressTask = Task.Run(async () =>
{
while (stream.Position < stream.Length)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
progress.Report((stream.Position, stream.Length));
}
});
await Task.WhenAll(readTask, progressTask);
return readTask.Result;
}
try
{
Task<string> readText = TextFileLoader.ReadTextAsync(filePath, progress, cts.Token);
content = await readText;
LabelProgress.Content = "Done Reading! Now creating wordlist...";
}
catch (OperationCanceledException)
{
LabelProgress.Content = "File laden wurde abgebrochen";
}
I have a buttonClick Event for cancel cts.Cancel(); but the only part where it works is the Dictionary creation. If I place the cancellationToken.ThrowIfCancellationRequested(); into the progressbar update part, it stops only the update, the stream reading still continues. If I place is right below var readTask = reader.ReadToEndAsync(); it does nothing.
You can get the current position while reading by checking Stream.Position property at regular interval. The following method will check the current position once per 100 milliseconds and report it by current value of progress parameter. To use this method, instantiate Progess<(double current, double total)> and subscribe to its ProgressChanged event.
public static async Task<string> ReadTextAsync(string filePath, IProgress<(double current, double total)> progress)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
var readTask = reader.ReadToEndAsync();
var progressTask = Task.Run(async () =>
{
while (stream.Position < stream.Length)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
progress.Report((stream.Position, stream.Length));
}
});
await Task.WhenAll(readTask, progressTask);
return readTask.Result;
}

Writing to StorageFile concurrently

How can I write some text to a StorageFile concurrently in Windows Phone 8.1? I tried to implement mutex provided by lock in C#, but I'm unable to open the stream of the file that I want to write to under in the lock block.
This is what I'm doing:
StorageFile file = await folder.GetFileAsync(logFileName);
// Lock file access object and open filestream for writing.
lock (fileLockObject)
{
StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync());
writer.Write(sometext);
}
But lock doesn't allow any kind of async operation in it's body. How can I still maintain mutex and concurrency without using some third-party library like AsyncLock?
In async operations you need use AsyncLock instead lock
private readonly AsyncLock asyncLocker = new AsyncLock();
.....
using(await asyncLocker.LockAsync())
{
enter code here
}
AsyncLock msdn, github
Usually in this scenario you want the writes to be in order as much as possible and you want to offload the actual writing to another single thread. Below is a simplified example however in most cases you will want to optimize it to end and restart the thread when needed.
AutoResetEvent _waitHandle = new AutoResetEvent(false);
List<string> _writeCache = new List<string>();
bool _threadRunning = false;
void WriteToFile(string text)
{
lock (_writeCache)
{
_writeCache.Add(text);
_waitHandle.Set();
if(_threadRunning)
{
return;
}
_threadRunning = true;
}
Task.Run(async () =>
{
while (true)
{
_waitHandle.WaitOne();
StorageFile file = await folder.GetFileAsync(logFileName);
using (var f = await file.OpenStreamForWriteAsync())
{
using (StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync()))
{
lock (_writeCache)
{
while (_writeCache.Count > 0)
{
string s = _writeCache[0];
_writeCache.RemoveAt(0);
writer.Write(s);
}
}
}
}
}
});
}
I was able to resolve this using Semaphores. I used SlimSemaphore() to acquire and release a semaphore with a single resource before and writing to file. This enabled use of await operation and removed the limitations associated with lock and mutex as you can run await operations in the block (which mutex and lock don't really allow).
// define semaphore with only one resource to allocate.
SemaphoreSlim semaphore = new SemaphoreSlim(1);
// acquire semaphore
await semaphore.WaitAsync();
try { // write to file }
catch { // catch any exception }
finally
{
semaphore.Release();
}
Since you do need to use a lock statement, you could try:
using System.Threading.Tasks;
public async Task WriteToFile(string text)
{
StorageFile file = await folder.GetFileAsync(logFileName);
lock (fileLockObject)
{
Task<Stream> streamTask = file.OpenStreamForWriteAsync();
try
{
streamTask.Wait();
}
catch (AggregateException ex)
{
// You may want to handle errors here
}
if (!streamTask.IsFaulted)
{
using (StreamWriter writer = new StreamWriter(streamTask.Result))
{
writer.Write(text);
}
}
}
}
Usage (supposed this is an event handler, else use public async Task YourMethod(...)):
public async void YourMethod()
{
// 1. Blocks UI thread
Task t = WriteToFile("text");
t.Wait();
// or
// 2. Does not block UI thread
await WriteToFile("text");
}
Try this code
public static void WriteLog(string Error)
{
using (StreamWriter logfile = new StreamWriter(filePath + "Log.txt", true))
{
logfile.WriteLine(DateTime.Now.ToString() + ":" + DateTime.Now.Millisecond.ToString() + " -#: " + Error);
logfile.Close();
}
}

how to display/report back progress of Task(Async) actions?

I have the need to move some processes to async. I have 5 methods that I need to call individually and have run in the background so the user can continue on with their work.
The test code below seems to work... but I haven't been able to figure out how to return information (message) indicating that the a task has completed. The class will be called from a separate windows form so that the progress can be displayed....
from the form:
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}
the class:
public static class TestTask
{
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
private static async Task SomethingAsync(IProgress<string> pProgress)
{
var t1 = SomeThing1(pProgress);
var t2 = SomeThing2(pProgress);
await Task.WhenAll(t1, t2);
if (pProgress != null) pProgress.Report(#"all tasks completed");
}
private static async Task SomeThing1()
{
await Task.Delay(9000);
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t1 completed");
}
private static async Task SomeThing2()
{
await Task.Delay(7000);
var filename = #"c:\temp\tt2.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t2 completed");
}
}
I would like know when each task has completed. Any help or direction would be appreciated.
EDIT
I have edited this post to reflect my changes... I still cannot get a progress report back to the UI... any thoughts?
You're doing IO bound work, you don't need to use thread-pool threads.
Transform your methods to use the async APIs of StreamWriter:
private static async Task FirstThingAsync()
{
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now);
}
}
Same for your second method, and then you can asynchronously wait on them concurrently:
private static async Task SomethingAsync()
{
var firstThing = FirstThingAsync();
var secondThing = SecondThingAsync();
await Task.WhenAll(firstThing, secondThing);
}
Edit:
You're never reaching your first Progress.Report call because your code is throwing an InvalidOperationException when you call t.Start() on a promise-style task:
t1.Start();
await t1;
t2.Start();
await t2;
The task returned from both method calls is a "hot task", meaning it's operation is already started. The docs on Task.Start say:
InvalidOperationException: The Task is not in a valid state to be
started. It may have already been started, executed, or canceled, or
it may have been created in a manner that doesn't support direct
scheduling.
The reason you're not seeing that exception is because you're swallowing it:
var t = SomethingAsync(pProgress);
When you don't await on the async operation. Your method calls should look like this:
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}

Form freezes for x seconds when zipping a folder in C#

I have a windows form project in which I zip a folder. The form freezes for 3-4 seconds and then the operation is done.
private void ZipSafe(string p_FolderName, string p_ArchiveName)
{
try
{
if (File.Exists(p_ArchiveName))
File.Delete(p_ArchiveName);
string[] l_DataSet = Directory.GetFiles(p_FolderName, "*.txt");
using (ZipArchive l_Zip = ZipFile.Open(p_ArchiveName, ZipArchiveMode.Create))
{
foreach (string l_File in l_DataSet)
{
using (FileStream l_Stream = new FileStream(l_File, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite))
{
ZipArchiveEntry l_ZipArchiveEntry = l_Zip.CreateEntry(Path.GetFileName(l_File), CompressionLevel.Optimal);
using (Stream l_Destination = l_ZipArchiveEntry.Open())
{
l_Stream.CopyTo(l_Destination);
}
}
}
l_Zip.Dispose();
}
}
catch (System.Exception e)
{
using (StreamWriter sw = new StreamWriter(#"C:\Users\**\Documents\ErrorZip.txt"))
{
string l = e.ToString();
sw.WriteLine(l);
sw.Close();
}
}
}
I call this function when clicking on a button. I have tried to use the debugger to understand what's going on here. The freeze happens during the second iteration of the for-each loop, for the following line of code:
l_Stream.CopyTo(l_Destination);
I know there has been a lot of post about zipping folders in c#, still I hope my question is relevant. Any help would be great, thank in advance guy.
Have a good day,
vbvx
This happens because you are using the UI thread to carry out the operation. You need to run the operation on another thread to leave the UI thread free (i.e. stopping your application from freezing).
try:
private void ZipSafe(string p_FolderName, string p_ArchiveName)
{
Task.Factory.StartNew(() =>
{
try
{
if (File.Exists(p_ArchiveName))
File.Delete(p_ArchiveName);
string[] l_DataSet = Directory.GetFiles(p_FolderName, "*.txt");
using (ZipArchive l_Zip = ZipFile.Open(p_ArchiveName, ZipArchiveMode.Create))
{
foreach (string l_File in l_DataSet)
{
using (FileStream l_Stream = new FileStream(l_File, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite))
{
ZipArchiveEntry l_ZipArchiveEntry = l_Zip.CreateEntry(Path.GetFileName(l_File), CompressionLevel.Optimal);
using (Stream l_Destination = l_ZipArchiveEntry.Open())
{
l_Stream.CopyTo(l_Destination);
}
}
}
l_Zip.Dispose();
}
}
catch (System.Exception e)
{
using (StreamWriter sw = new StreamWriter(#"C:\Users\**\Documents\ErrorZip.txt"))
{
string l = e.ToString();
sw.WriteLine(l);
sw.Close();
}
}
}
}

STA Threading, Rtf to HTML converter issues

I'm using this code to attempt to convert my Rtf text from a RichTextBox in my UI to Html. I have 2 projects in my solution: the UI & application written in MVVM- IsesTextEditor and the pre-written Converter as provided in the link- MarkupConverter.
A btn in my view is bound to a command in my view model which passes the RichTextBox text into the ConvertRtfToHtml method as per the example:
private string ConvertRtfToHtml(string PastedText)
{
var thread = new Thread(ConvertRtfInSTAThread);
var threadData = new ConvertRtfThreadData { RtfText = PastedText };
thread.SetApartmentState(ApartmentState.STA);
thread.Start(threadData);
thread.Join();
return threadData.HtmlText;
}
private void ConvertRtfInSTAThread(object rtf)
{
var threadData = rtf as ConvertRtfThreadData;
threadData.HtmlText = markupConverter.ConvertRtfToHtml(threadData.RtfText);
}
private class ConvertRtfThreadData
{
public string RtfText { get; set; }
public string HtmlText { get; set; }
}
The MarkupConverter.ConvertRtfToHtml method then calls ConvertRtfToXaml which instantiates a new RichTextBox object:
public static class RtfToHtmlConverter
{
private const string FlowDocumentFormat = "<FlowDocument>{0}</FlowDocument>";
public static string ConvertRtfToHtml(string rtfText)
{
var xamlText = string.Format(FlowDocumentFormat, ConvertRtfToXaml(rtfText));
return HtmlFromXamlConverter.ConvertXamlToHtml(xamlText, false);
}
private static string ConvertRtfToXaml(string rtfText)
{
var richTextBox = new RichTextBox();
if (string.IsNullOrEmpty(rtfText)) return "";
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
{
return rtfStreamReader.ReadToEnd();
}
}
}
}
At creating the RichTextBox object I'm getting a The calling thread cannot access this object because a different thread owns it. exception.
Can anyone suggest a fix for this? I'm sure it's a relatively simple threading issue but I'm a junior dev & don't have much experience with threading or STA...
As the exception suggest, move the GUI related code from work thread to the GUI thread.
Please refer this post, I copied some text from it:
Like the frames of the user interface, like many Windows Forms, WPF also imposes a single threading model, which means you can only access a specified derivative DispatcherObject thread that creates it. In Windows Forms controls that implement the interface ISynchronizeInvoke, this interface exposes a set of methods such as Invoke and BeginInvoke to impose a contract common thread synchronization we can use to access a control from another thread. In WPF, we also have that kind of thing, but these operations are involved in a class called Dispatcher, Dispatcher WPF is the way to allow this kind of thread synchronization model.
To use a WPF control on a thread which is not the main UI thread, you need to do some plumbing, like start and finish the WPF Dispatcher loop.
I've put together a sample app app showing how to do this, using some helper code I posted earlier here.
This is a console app, although you should be able to use RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result in any other execution environment.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Threading;
namespace ConsoleApplication_22717365
{
// by Noseratio - https://stackoverflow.com/q/22717365/1768303
public class Program
{
const string RTF = #"{\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard This is some {\b bold} text.\par}";
static void Main()
{
var xaml = RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result;
Console.WriteLine(xaml);
}
// http://code.msdn.microsoft.com/windowsdesktop/Converting-between-RTF-and-aaa02a6e
private static string ConvertRtfToXaml(string rtfText)
{
var richTextBox = new RichTextBox();
if (string.IsNullOrEmpty(rtfText)) return "";
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
{
return rtfStreamReader.ReadToEnd();
}
}
}
// https://stackoverflow.com/a/22626704/1768303
public static async Task<TResult> RunOnWpfThreadAsync<TResult>(Func<Task<TResult>> funcAsync)
{
var tcs = new TaskCompletionSource<Task<TResult>>();
Action startup = async () =>
{
// this runs on the WPF thread
var task = funcAsync();
try
{
await task;
}
catch
{
// propagate exception with tcs.SetResult(task)
}
// propagate the task (so we have the result, exception or cancellation)
tcs.SetResult(task);
// request the WPF tread to end
// the message loop inside Dispatcher.Run() will exit
System.Windows.Threading.Dispatcher.ExitAllFrames();
};
// the WPF thread entry point
ThreadStart threadStart = () =>
{
// post the startup callback
// it will be invoked when the message loop starts pumping
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
startup, DispatcherPriority.Normal);
// run the WPF Dispatcher message loop
System.Windows.Threading.Dispatcher.Run();
};
// start and run the STA thread
var thread = new Thread(threadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
try
{
// propagate result, exception or cancellation
return await tcs.Task.Unwrap().ConfigureAwait(false);
}
finally
{
// make sure the thread has fully come to an end
thread.Join();
}
}
// a wrapper to run synchronous code
public static Task<TResult> RunOnWpfThreadAsync<TResult>(Func<TResult> func)
{
return RunOnWpfThreadAsync(() => Task.FromResult(func()));
}
}
}

Categories

Resources