PDA

View Full Version : درج درصد پیشرفت کپی شدن فایل بر روی Progressbar



tadeh2010
یک شنبه 18 مهر 1395, 00:51 صبح
با سلام دوستان
در لینک زیر برنامه ای نوشته ام که یک کار ساده میکند
فایلی را کپی میکند و پیشرفت کار را با پروگرسبار نمایش داده ام

توضیح :
لطفا نگویید که این کار باتوابع خود دات نت به سادگی انجام میشود!
چون خودم در جستجوهایم بهش برخورد کردم ولی من آن را نمی خواهم
برنامه ای که درست کرده ام از BackgroundWorker استفاده کرده ام و یک پروگرس بار dev
و داخل Do work در یک حلقه (با استفاده از متغییر b=10) گفته ام که ده بایت ده بایت از فایل مبدا بخواند و بریزد در فایل مقصد
b=10 را برای کاهش سرعت و چک کردن progressbar و برنامه قرار داده ام که بعدا قابل افزایش است

مشکل :
وقتی درصد پیشرفت را به برنامه اضافه میکنم به مانند این است که یک تسک اضافه به برنامه داده باشیم و بدون در نظر گرفتن Thread BackgroundWorker هم سرعت کپی کم میشود و هم موقع کپی دکمه Cancel و فرم به حالت هنگ در میآید
مشکل از کجاست؟
شما دوستان چه راه حلی را پیشنهاد میکنید؟:متفکر:

کد برنامه :

namespace BackgroundProgress
{
public partial class Form1 : Form
{
BackgroundWorker m_oWorker;
public string SPath, DPath;
public FileStream fsInput, fsDes;
long flen;

public Form1()
{
InitializeComponent();
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler
(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Task Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error while performing background operation.");
}
else
{
MessageBox.Show("Copy is successful");
}
fsInput.Close();
fsDes.Close();
btn_copy.Enabled = true;
btn_cancel.Enabled = false;
}
private void btn_open_Click(object sender, EventArgs e)
{
OpenFileDialog OpenDialog = new OpenFileDialog();
OpenDialog.Filter = "ALL Files|*.*";
OpenDialog.ShowDialog();
SPath = OpenDialog.FileName;
if (System.IO.File.Exists(SPath))
{
TB_Source.Text = SPath;
TB_Destination.Enabled = true;
btn_save.Enabled = true;
label3.Enabled = true;
}
else
{
TB_Destination.Enabled = false;
btn_save.Enabled = false;
label3.Enabled = false;
}
}
private void btn_save_Click(object sender, EventArgs e)
{
SaveFileDialog SaveDialog = new SaveFileDialog();
SaveDialog.Filter = "ALL Files|*.*";
SaveDialog.ShowDialog();
DPath = SaveDialog.FileName;

{
TB_Destination.Text = DPath;
btn_copy.Enabled = true;
btn_copy.Focus();
}
}
private void Form1_Load(object sender, EventArgs e)
{
TB_Destination.Text = "";
TB_Source.Text = "";
}
private void btn_cancel_Click(object sender, EventArgs e)
{
if (m_oWorker.IsBusy)
{
m_oWorker.CancelAsync();
}
}

private void btn_copy_Click(object sender, EventArgs e)
{
btn_copy.Enabled = false;
btn_cancel.Enabled = true;

fsInput = new FileStream(SPath, FileMode.Open, FileAccess.Read);
fsDes = new FileStream(DPath, FileMode.Create, FileAccess.Write);
flen = fsInput.Length;
m_oWorker.RunWorkerAsync();
}
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBarX1.Value = e.ProgressPercentage;
//progressBarX1.Text = "Processing......" + progressBarX1.Value.ToString() + "%";
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
int n = 0;
int b = 10;
progressBarX1.Step = b;
//progressBarX1.Value = 0;
progressBarX1.Maximum = (int)flen / b;
byte[] bytearrayinput = new byte[b];
do
{
//Thread.Sleep(1);
Application.DoEvents();
n = fsInput.Read(bytearrayinput, 0, b);
fsDes.Write(bytearrayinput, 0, b);
m_oWorker.ReportProgress(i);
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
i++;
} while (n > 0);
m_oWorker.ReportProgress(100);
}
}
}

Mahmoud.Afrad
یک شنبه 18 مهر 1395, 05:41 صبح
فریز شدن UI به این دلیل اتفاق میفته که میزان بروزرسانی رابط کاربری(progressbar) زیاد هست. برای حل این موضوع ، در صورتی بروزرسانی رو انجام بدید که تغییر محسوسی در روند پیشرفت کپی فایل بوجود اومده باشه. برای اینکه متوجه تغییر محسوس بشید باید معیار سنجش رو نسبت به تعداد بایتهای فایل منبع بسنجید؛ مثلا، بیشترین مقدار progressbar رو 100 بدید، کپی شدن فایل رو به درصد تبدیل کنید و در صورتی که مقدار درصد پیشرفت تغییر کرده باشد، ReportProgress را فراخوانی کنید.



کدتون یک باگ هم داره و واون اینکه تعداد بایتهای فایل مقصد همیشه مضربی از تعداد بایتهای بافر خواهد بود که باید اصلاح بشه.

tadeh2010
یک شنبه 18 مهر 1395, 14:28 عصر
سلام مهندس عزیز و دوست گرامی
بسیار ممنونم:لبخندساده:
یعنی چطور؟
این طریقه را هم انجام داده ام ولی نشد
یعنی همون b را با استفاده از فرمول b=flen/100 بدست آوردم و max را 100 گزاشتم ولی فرقی نکرد:ناراحت: و هنوز فرم فریز میشه

درمورد اون باگ هم مگر فایل eof ندارد خب eof وقتی نوشته بشود فایل بسته میشود، نه؟

Mahmoud.Afrad
یک شنبه 18 مهر 1395, 21:42 عصر
در مورد محاسبه درصد، flen / b تعداد کل stepها خواهد بود، پس i*100/(flen / b) میشه درصد پیشرفت. باید با درصد پیشرفت قبلی مقایسه بشه و در صورت متفاوت بودن پروگرسبار بروز بشه.


در مورد باگی هم که گفتم، مثلا اگر فایل وروی 13 بایت باشه، با step = 10 فایل خروجی 20 بایت خواهد بود.

tadeh2010
دوشنبه 19 مهر 1395, 13:22 عصر
سلام این چطوره منظورتون اینطوریه؟

namespace BackgroundProgress
{
public partial class Form1 : Form
{
BackgroundWorker m_oWorker;
public string SPath, DPath;
public FileStream fsInput, fsDes;
long flen;
int progress_old;

public Form1()
{
InitializeComponent();
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler
(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Task Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error while performing background operation.");
}
else
{
MessageBox.Show("Copy is successful");
m_oWorker.ReportProgress(0);
}
fsInput.Close();
fsDes.Close();
btn_copy.Enabled = true;
btn_cancel.Enabled = false;
btn_copy.Focus();
}
private void btn_open_Click(object sender, EventArgs e)
{
OpenFileDialog OpenDialog = new OpenFileDialog();
OpenDialog.Filter = "ALL Files|*.*";
OpenDialog.ShowDialog();
SPath = OpenDialog.FileName;
if (System.IO.File.Exists(SPath))
{
TB_Source.Text = SPath;
TB_Destination.Enabled = true;
btn_save.Enabled = true;
label3.Enabled = true;
}
else
{
TB_Destination.Enabled = false;
btn_save.Enabled = false;
label3.Enabled = false;
}
}
private void btn_save_Click(object sender, EventArgs e)
{
SaveFileDialog SaveDialog = new SaveFileDialog();
SaveDialog.Filter = "ALL Files|*.*";
SaveDialog.ShowDialog();
DPath = SaveDialog.FileName;
{
TB_Destination.Text = DPath;
btn_copy.Enabled = true;
btn_copy.Focus();
}
}
private void Form1_Load(object sender, EventArgs e)
{
TB_Destination.Text = "";
TB_Source.Text = "";
TB_Source.Focus();
}
private void btn_cancel_Click(object sender, EventArgs e)
{
if (m_oWorker.IsBusy)
{
m_oWorker.CancelAsync();
}
}
private void btn_copy_Click(object sender, EventArgs e)
{
btn_copy.Enabled = false;
btn_cancel.Enabled = true;
fsInput = new FileStream(SPath, FileMode.Open, FileAccess.Read);
fsDes = new FileStream(DPath, FileMode.Create, FileAccess.Write);
flen = fsInput.Length;
m_oWorker.RunWorkerAsync();
btn_cancel.Focus();
}
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBarX1.Value = e.ProgressPercentage;
if (e.ProgressPercentage > progress_old)
{
progressBarX1.Text = progressBarX1.Value.ToString() + "%";
progress_old = e.ProgressPercentage;
}
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
int n = 0;
//int b = (int)flen/100;
int b = 10;
progressBarX1.Step = b;
//progressBarX1.Value = 0;
//progressBarX1.Maximum = 100;
progress_old = 0;
progressBarX1.Maximum = (int)flen / b;
byte[] bytearrayinput = new byte[b];
do
{
Thread.Sleep(1);
//Application.DoEvents();
n = fsInput.Read(bytearrayinput, 0, b);
fsDes.Write(bytearrayinput, 0, n);
m_oWorker.ReportProgress(i);
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
i++;
} while (n > 0);
m_oWorker.ReportProgress(100);
}
}
}

Mahmoud.Afrad
دوشنبه 19 مهر 1395, 15:37 عصر
در متد DoWork به هیچ کنترل و قسمتی از UI نمیتونید و نباید دسترسی داشته باشید.
در مورد درصد که توضیح دادم. progressBarX1.Maximum را 100 بدید و در داخل حلقه درصد رو محاسبه و ...
در مورد باگ هم باید بگم که در صورت نیاز باید طول بافر رو داخل حلقه اصلاح کنید.

من کدتون رو اصلاح کردم ولی دوست دارم خودتون به نتیجه برسید.

tadeh2010
چهارشنبه 21 مهر 1395, 20:06 عصر
با سلام و تشکر از راهنمایی های دوستان
فکر کنم این کد درست باشد
قبلا در مورد آن باگ هم بگویم که مقدار بایتهای خوانده شده در متغییر n ریخته میشود توسط متد fsInput.Read و موقع نوشتن بایتها من از متغییر n استفاده کرده ام، پس فکر میکنم بایت اضافه در آخرین مرتبه نوشته نمی شود.:متفکر:
کد برنامه:


namespace BackgroundProgress
{
public partial class Form1 : Form
{
BackgroundWorker m_oWorker;
public string SPath, DPath;
public FileStream fsInput, fsDes;
long flen;
int progress_old;
int b = 10;
public Form1()
{
InitializeComponent();
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler
(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Task Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error while performing background operation.");
}
else
{
MessageBox.Show("Copy is successful");
progressBarX1.Value = 0;
}
fsInput.Close();
fsDes.Close();
btn_copy.Enabled = true;
btn_cancel.Enabled = false;
btn_copy.Focus();
}
private void btn_open_Click(object sender, EventArgs e)
{
OpenFileDialog OpenDialog = new OpenFileDialog();
OpenDialog.Filter = "ALL Files|*.*";
OpenDialog.ShowDialog();
SPath = OpenDialog.FileName;
if (System.IO.File.Exists(SPath))
{
TB_Source.Text = SPath;
TB_Destination.Enabled = true;
btn_save.Enabled = true;
label3.Enabled = true;
TB_Destination.Focus();
}
else
{
TB_Destination.Enabled = false;
btn_save.Enabled = false;
label3.Enabled = false;
}
}
private void btn_save_Click(object sender, EventArgs e)
{
SaveFileDialog SaveDialog = new SaveFileDialog();
SaveDialog.Filter = "ALL Files|*.*";
SaveDialog.ShowDialog();
DPath = SaveDialog.FileName;
{
TB_Destination.Text = DPath;
btn_copy.Enabled = true;
btn_copy.Focus();
}
}
private void Form1_Load(object sender, EventArgs e)
{
TB_Source.Focus();
}
private void btn_cancel_Click(object sender, EventArgs e)
{
if (m_oWorker.IsBusy)
m_oWorker.CancelAsync();
}
private void btn_copy_Click(object sender, EventArgs e)
{
btn_copy.Enabled = false;
btn_cancel.Enabled = true;
fsInput = new FileStream(SPath, FileMode.Open, FileAccess.Read);
fsDes = new FileStream(DPath, FileMode.Create, FileAccess.Write);
flen = fsInput.Length;
progressBarX1.Step = (int)flen / b;
progressBarX1.Maximum = 100;
progress_old = 0;
m_oWorker.RunWorkerAsync();
progressBarX1.Text = "0%";
btn_cancel.Focus();
}
private void TB_Source_TextChanged(object sender, EventArgs e)
{
SPath = TB_Source.Text;
if (System.IO.File.Exists(SPath))
{
TB_Source.Text = SPath;
TB_Destination.Enabled = true;
btn_save.Enabled = true;
label3.Enabled = true;
}
else
{
TB_Destination.Enabled = false;
btn_save.Enabled = false;
label3.Enabled = false;
btn_copy.Enabled = false;
}
}
private void TB_Destination_TextChanged(object sender, EventArgs e)
{
DPath = TB_Destination.Text;
if (System.IO.File.Exists(DPath))
{
TB_Destination.Text = DPath;
btn_copy.Enabled = true;
btn_copy.Focus();
}
else
{
btn_copy.Enabled = false;
}
}
void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBarX1.Value = e.ProgressPercentage;
progressBarX1.Text = progressBarX1.Value.ToString() + "%";
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
int n = 0;
int p = 0;
byte[] bytearrayinput = new byte[b];
do
{
//Thread.Sleep(1);
//Application.DoEvents();
n = fsInput.Read(bytearrayinput, 0, b);
fsDes.Write(bytearrayinput, 0, n);
p = (int)((i * 100) / (flen / b));
if (p > progress_old)
{
progress_old = p;
m_oWorker.ReportProgress(p);
}
if (m_oWorker.CancellationPending)
{
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
i++;
} while (n > 0);
m_oWorker.ReportProgress(100);
}
}
}
:لبخندساده:

Mahmoud.Afrad
چهارشنبه 21 مهر 1395, 21:16 عصر
نام کنترلها و ... رو تغییر دادم اما مشخص هست ...
public partial class FrmMain : Form
{
private BackgroundWorker _myWorker;
private string _sourcePath, _destinationPath;

public FrmMain()
{
InitializeComponent();

_myWorker = new BackgroundWorker();
_myWorker.DoWork += _myWorker_DoWork;
_myWorker.ProgressChanged += _myWorker_ProgressChanged;
_myWorker.RunWorkerCompleted += _myWorker_RunWorkerCompleted;
_myWorker.WorkerReportsProgress = true;
_myWorker.WorkerSupportsCancellation = true;
}

private void btnSource_Click(object sender, EventArgs e)
{
OpenFileDialog openDialog = new OpenFileDialog {Filter = @"ALL Files|*.*"};
if (openDialog.ShowDialog() == DialogResult.OK)
{
_sourcePath = openDialog.FileName;
if (File.Exists(_sourcePath))
{
txtSource.Text = _sourcePath;
txtDestination.Enabled = true;
btnDestination.Enabled = true;
label3.Enabled = true;
}
else
{
txtDestination.Enabled = false;
btnDestination.Enabled = false;
label3.Enabled = false;
}
}
}

private void btnDestination_Click(object sender, EventArgs e)
{
SaveFileDialog saveDialog = new SaveFileDialog {Filter = @"ALL Files|*.*"};
if (saveDialog.ShowDialog() == DialogResult.OK)
{
_destinationPath = saveDialog.FileName;
txtDestination.Text = _destinationPath;
btnCopy.Enabled = true;
btnCopy.Focus();
}
}

private void btnCopy_Click(object sender, EventArgs e)
{
btnCopy.Enabled = false;
btnCancel.Enabled = true;
progressBar1.Value = 0;
progressBar1.Maximum = 100;
_myWorker.RunWorkerAsync();
}

private void btnCancel_Click(object sender, EventArgs e)
{
if (_myWorker.IsBusy)
{
_myWorker.CancelAsync();
}
}

void _myWorker_DoWork(object sender, DoWorkEventArgs e)
{
using (FileStream sourceFileStream = new FileStream(_sourcePath, FileMode.Open, FileAccess.Read))
using (FileStream destinationFileStream = new FileStream(_destinationPath, FileMode.Create, FileAccess.Write))
{
int bufferLength = 10;
long countOfTotalSteps = sourceFileStream.Length / bufferLength;
int currentStep = 0;
byte[] bufferBytes = new byte[bufferLength];
int oldProgressValue = 0;
int newProgressValue = 0;
do
{
long remainBytes = sourceFileStream.Length - sourceFileStream.Position;
if (remainBytes < bufferLength)
{
bufferBytes = new byte[remainBytes];
bufferLength = (int) remainBytes;
}
sourceFileStream.Read(bufferBytes, 0, bufferLength);
destinationFileStream.Write(bufferBytes, 0, bufferLength);
if (countOfTotalSteps > 0)
{
newProgressValue = (int) (currentStep * 100 / countOfTotalSteps);
if (newProgressValue != oldProgressValue)
{
_myWorker.ReportProgress(newProgressValue);
oldProgressValue = newProgressValue;
}
}

if (_myWorker.CancellationPending)
{
e.Cancel = true;
_myWorker.ReportProgress(0);
return;
}
currentStep++;

} while (sourceFileStream.Position < sourceFileStream.Length);
_myWorker.ReportProgress(100);
}
}

void _myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
progressBar1.Text = @"Processing..." + e.ProgressPercentage + @"%";
}

void _myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show(@"Copying file Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show(@"Error while copying file.");
}
else
{
MessageBox.Show(@"Copying file was successful");
}
btnCopy.Enabled = true;
btnCancel.Enabled = false;
}
}

ScienceLover
چهارشنبه 21 مهر 1395, 21:47 عصر
می تونید سربار اجرا را با System.Threading.Thread.Sleep(milisecond) کم کنید که در کد شما


void _myWorker_DoWork(object sender, DoWorkEventArgs e) {

using (FileStream sourceFileStream = new FileStream(_sourcePath, FileMode.Open, FileAccess.Read))
using (FileStream destinationFileStream = new FileStream(_destinationPath, FileMode.Create, FileAccess.Write))
{
int bufferLength = 10;
long countOfTotalSteps = sourceFileStream.Length / bufferLength;
int currentStep = 0;
byte[] bufferBytes = new byte[bufferLength];
int oldProgressValue = 0;
int newProgressValue = 0;
do
{
long remainBytes = sourceFileStream.Length - sourceFileStream.Position;
if (remainBytes < bufferLength)
{
bufferBytes = new byte[remainBytes];
bufferLength = (int) remainBytes;
}
sourceFileStream.Read(bufferBytes, 0, bufferLength);
destinationFileStream.Write(bufferBytes, 0, bufferLength);
if (countOfTotalSteps > 0)
{
newProgressValue = (int) (currentStep * 100 / countOfTotalSteps);
if (newProgressValue != oldProgressValue)
{
_myWorker.ReportProgress(newProgressValue);
oldProgressValue = newProgressValue;
}
}

if (_myWorker.CancellationPending)
{
e.Cancel = true;
_myWorker.ReportProgress(0);
return;
}
currentStep++;

} while (sourceFileStream.Position < sourceFileStream.Length);
_myWorker.ReportProgress(100);

System.Threading.Thread.Sleep(10); }

}


می شود

tadeh2010
چهارشنبه 21 مهر 1395, 22:21 عصر
سلام
پیغام ام را که همینجا فرستادم هنوز ندیده اید؟
این که اول متغییرهایتان underline میگذارید دلیل خاصی دارد یا دلبخواهیه !؟
@ ها چی!؟ اونها رو فکر میکردم فقط میزارن که اگر آدرس در رشته بود نیازی به دوتا اسلش نباشد، نه؟
من از این فورمول( i*100/(flen / b) ) استفاده کرده ام که در پست 4 گفته بودید ولی خودتان که استفاده نکرده اید !؟
بازهم ممنونم.:لبخندساده:

Mahmoud.Afrad
چهارشنبه 21 مهر 1395, 23:26 عصر
می تونید سربار اجرا را با System.Threading.Thread.Sleep(milisecond) کم کنید
...

اصلا در چنین کدی نیاز به Sleep نیست و در صورت استفاده از آن، زمان عملیات افزایش پیدا میکند. دلیل سربار اجرا به خاطر تکرار بیش از حد و با فاصله زمانی کم حلقه و دلیل این موضوع هم کوچک بودن بافر هست. پس با افزایش بافر مشکل حل میشه. که البته بهتره در اینگونه برنامه ها قابلیت تنظیم میزان بافر توسط کاربر وجود داشته باشد.
int bufferLength = 32768; // 32KB


سلام
پیغام ام را که همینجا فرستادم هنوز ندیده اید؟
این که اول متغییرهایتان underline میگذارید دلیل خاصی دارد یا دلبخواهیه !؟
@ ها چی!؟ اونها رو فکر میکردم فقط میزارن که اگر آدرس در رشته بود نیازی به دوتا اسلش نباشد، نه؟
من از این فورمول( i*100/(flen / b) ) استفاده کرده ام که در پست 4 گفته بودید ولی خودتان که استفاده نکرده اید !؟
بازهم ممنونم.:لبخندساده:

آندرلاین دلخواه هست. دلیل استفاده هم این هست که از متغیرها و پارامترهای محلی متدها قابل تشخیص باشه.
@ هم برای همون چیزی هست که گفتید بعلاوه اینکه میتونید رشته چند خطی داشته باشید.
در مورد درصد گرفتن ، کدش در خط 92 هست.