Click here to Skip to main content
15,065,044 members
Articles / Programming Languages / C#
Tip/Trick
Posted 16 Jun 2021

Tagged as

Stats

5.3K views
3 bookmarked

How to Action a Spinner in a Console Application

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
16 Jun 2021CPOL2 min read
How to add a Spinner to a long-running library method
In this tip, you will learn how to use a generic method to add a spinner to a library method running in a Console application.

Introduction

A spinner is used in conjunction with a time-consuming silent method to give an indication that work is progressing and that the application has not stalled. Adding a spinner to the source code is straight forward, it's just a question of making sure that the marathon method outputs some sort of active display as it progresses. It's not quite so simple when the method forms part of a sealed library as it's necessary to implement a degree of thread management between the library method running on one thread and the spinner running on a different thread.

A Spinner Class

One way that spinner functionality can be implemented is to instantiate a class that actions a new thread in its Start method and runs some sort of active display on that thread until the long running method completes. Here’s an example:

C#
public class Spinner
 {
     private readonly int delay;
     private bool isRunning = false;
     private Thread thread;
     public Spinner(int delay = 25)
     {
         this.delay = delay;
     }

     public void Start()
     {
         if (!isRunning)
         {
             isRunning = true;
             thread = new Thread(Spin);
             thread.Start();
         }
     }
     public void Stop()
     {
         isRunning = false;
     }

     private void Spin()
     {
         while (isRunning)
         {
             Console.Write('.');
             Thread.Sleep(delay);
         }
     }
 }

It can be used like this:

C#
public class Program
{
   static void Main()
    {
        int lifeValue=42;
        var spinner = new Spinner();
        spinner.Start();
        int meaningOfLife = LongLifeMethod(lifeValue);
        spinner.Stop();
        Console.WriteLine($"\r\nThe meaning of life is {meaningOfLife}");
        Console.ReadLine();
    }

   private static int LongLifeMethod(int lifeValue)
    {
        Thread.Sleep(3000);
        return lifeValue;
    }
}

There’s a hidden gotcha here. If the marathon method throws an exception, the Stop method will not be run. So it’s best to call the Stop method from inside a finally block to make sure that the spinner always stops spinning. But this adds a bit more complexity to the code to the extent that the Spinner class now looks like it needs a spinner service to manage it. There must be an easier way.

An Alternative Approach

The trick here is to use a method that's self-determining so that it switches itself off and can be used on a fire and forget basis. The Task-based Asynchronous Pattern can encapsulate all the required functionality within a single generic method.

C#
public static TResult RunWithSpinner<TResult>
(Func<TResult> longFunc, Action<CancellationToken> spinner)
 {
     CancellationTokenSource cts = new();
     //run the spinner on its own thread
     Task.Run(() => spinner(cts.Token));
     TResult result;
     try
     {
         result = longFunc();
     }
     finally
     {
         //cancel the spinner
         cts.Cancel();
     }
     return result;
 }

Using the Method

The method is used like this:

C#
static void Main()
{
    int lifeValue = 42;
    int meaningOfLife = RunWithSpinner(() => LongLifeMethod(lifeValue), Spin);
    Console.WriteLine($"\r\nThe meaning of life is {meaningOfLife}");
}
private static void Spin(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        Console.Write('.');
        Thread.Sleep(25);
    }
}

The longFunc parameter of the RunWithSpinner method is expressed as a lambda expression. The empty brackets on the left side of the => characters signify that the required method’s signature has no parameters and the call to LongLifeMethod leads the compiler to infer that the returned value is that method’s return value. So, at compile time, it will compile the lambda expression into an anonymous function that calls the LongLifeMethod and returns an int. Although the function itself does not take any parameters, it calls the LongLifeMethod and uses the captured variable, lifeValue, as a parameter for that method. The technique of using captured variables in this manner is very powerful and is commonly used in Linq expressions.

Conclusion

Generic methods can be useful for encapsulating, in a few lines of code, the kind of functionality that usually requires a class instance. In this case, the RunWithSpinner method removes the need for a Spinner class along with the code that's needed to manage it.

History

  • 16th June, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

George Swan
Student
Wales Wales
No Biography provided

Comments and Discussions

 
QuestionMisunderstanding of try/finally and object lifetime Pin
Stacy Dudovitz19-Jun-21 22:07
professionalStacy Dudovitz19-Jun-21 22:07 
AnswerRe: Misunderstanding of try/finally and object lifetime Pin
George Swan20-Jun-21 1:52
MemberGeorge Swan20-Jun-21 1:52 
GeneralRe: Misunderstanding of try/finally and object lifetime Pin
Stacy Dudovitz21-Jun-21 8:37
professionalStacy Dudovitz21-Jun-21 8:37 
GeneralRe: Misunderstanding of try/finally and object lifetime Pin
George Swan21-Jun-21 12:14
MemberGeorge Swan21-Jun-21 12:14 
SuggestionMisleading article title Pin
Stylianos Polychroniadis17-Jun-21 4:47
MemberStylianos Polychroniadis17-Jun-21 4:47 
GeneralRe: Misleading article title Pin
George Swan17-Jun-21 5:23
MemberGeorge Swan17-Jun-21 5:23 
GeneralRe: Misleading article title Pin
Gary R. Wheeler21-Jun-21 11:19
MemberGary R. Wheeler21-Jun-21 11:19 
GeneralRe: Misleading article title Pin
George Swan21-Jun-21 21:43
MemberGeorge Swan21-Jun-21 21:43 
GeneralRe: Misleading article title Pin
sx200822-Jun-21 11:15
Membersx200822-Jun-21 11:15 
GeneralRe: Misleading article title Pin
George Swan22-Jun-21 13:05
MemberGeorge Swan22-Jun-21 13:05 
GeneralRe: Misleading article title Pin
Stylianos Polychroniadis22-Jun-21 23:01
MemberStylianos Polychroniadis22-Jun-21 23:01 
Antisthenes (Greek philosopher 446—366 B.C.E.) used to say "The beginning of wisdom is visiting the words". Plato (Greek philosopher 428-348 B.C.E) was teaching the importance of accuracy of logos (speech) and the dangers of misuse of words. As a programmer you will learn, hopefully, the importance of accurately naming variables, functions, etc. Just my two cents, take care.
GeneralRe: Misleading article title Pin
George Swan23-Jun-21 2:04
MemberGeorge Swan23-Jun-21 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.